bug fix in cheque in custom footer text in documents

This commit is contained in:
Hesabix 2025-04-16 18:59:21 +00:00
parent baae7232e2
commit f25b6ab8dc
14 changed files with 639 additions and 194 deletions

View file

@ -64,6 +64,32 @@ class ChequeController extends AbstractController
->getQuery() ->getQuery()
->getResult(); ->getResult();
$allCheques = array_merge($chequesInput, $chequesOutput);
foreach ($allCheques as $cheque) {
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['cheque' => $cheque]);
foreach ($rows as $row) {
$doc = $row->getDoc();
if(count($doc->getHesabdariRows()) == 1){
$exrow = $doc->getHesabdariRows()[0];
$newrow = new HesabdariRow();
$newrow->setDoc($doc);
$newrow->setCheque($cheque);
$newrow->setYear($exrow->getYear());
$newrow->setBid($acc['bid']);
$newrow->setRef($entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => 126]));
if($exrow->getBs() == '0'){
$newrow->setBs($exrow->getBd());
$newrow->setBd(0);
}else{
$newrow->setBs(0);
$newrow->setBd($exrow->getBs());
}
$newrow->setDes($exrow->getDes());
$entityManager->persist($newrow);
$entityManager->flush();
}
}
}
return $this->json([ return $this->json([
'input' => Explore::SerializeCheques(array_reverse($chequesInput)), 'input' => Explore::SerializeCheques(array_reverse($chequesInput)),
'output' => Explore::SerializeCheques(array_reverse($chequesOutput)) 'output' => Explore::SerializeCheques(array_reverse($chequesOutput))

View file

@ -104,6 +104,9 @@ class PrintersController extends AbstractController
$temp['rfsell']['paper'] = 'A4-L'; $temp['rfsell']['paper'] = 'A4-L';
} }
$temp['global']['leftFooter'] = $settings->getLeftFooter();
$temp['global']['rightFooter'] = $settings->getRightFooter();
return $this->json($temp); return $this->json($temp);
} }
@ -168,6 +171,9 @@ class PrintersController extends AbstractController
$settings->setFastsellInvoice($params['fastsell']['invoice']); $settings->setFastsellInvoice($params['fastsell']['invoice']);
$settings->setFastsellPdf($params['fastsell']['pdf']); $settings->setFastsellPdf($params['fastsell']['pdf']);
$settings->setLeftFooter($params['global']['leftFooter']);
$settings->setRightFooter($params['global']['rightFooter']);
$entityManager->persist($settings); $entityManager->persist($settings);
$entityManager->flush(); $entityManager->flush();
$log->insert('تنظیمات چاپ', 'تنظیمات چاپ به روز رسانی شد.', $this->getUser(), $acc['bid']->getId()); $log->insert('تنظیمات چاپ', 'تنظیمات چاپ به روز رسانی شد.', $this->getUser(), $acc['bid']->getId());

View file

@ -674,4 +674,37 @@ class StoreroomController extends AbstractController
); );
return $this->json(['id' => $pdfPid]); return $this->json(['id' => $pdfPid]);
} }
#[Route('/api/storeroom/exist/print', name: 'app_storeroom_exist_print')]
public function app_storeroom_exist_print(Printers $printers, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
{
$params = [];
if ($content = $request->getContent()) {
$params = json_decode($content, true);
}
$acc = $access->hasRole('store');
if (!$acc)
throw $this->createAccessDeniedException();
// لود کردن اطلاعات انبار از دیتابیس
$storeroom = $entityManager->getRepository(Storeroom::class)->find($params['storeroom']);
if (!$storeroom) {
throw $this->createNotFoundException('انبار مورد نظر یافت نشد');
}
$pdfPid = 0;
$pdfPid = $provider->createPrint(
$acc['bid'],
$this->getUser(),
$this->renderView('pdf/printers/storeroom/exist.html.twig', [
'title' => 'سنجش موجودی انبار',
'bid' => $acc['bid'],
'storeroom' => $storeroom,
'items' => $params['items']
]),
false
);
return $this->json(['id' => $pdfPid]);
}
} }

View file

@ -120,6 +120,12 @@ class PrintOptions
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private ?bool $fastsellCashdeskTicket = null; private ?bool $fastsellCashdeskTicket = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $leftFooter = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $rightFooter = null;
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -544,4 +550,28 @@ class PrintOptions
return $this; return $this;
} }
public function getLeftFooter(): ?string
{
return $this->leftFooter;
}
public function setLeftFooter(?string $leftFooter): static
{
$this->leftFooter = $leftFooter;
return $this;
}
public function getRightFooter(): ?string
{
return $this->rightFooter;
}
public function setRightFooter(?string $rightFooter): static
{
$this->rightFooter = $rightFooter;
return $this;
}
} }

View file

@ -3,11 +3,8 @@
namespace App\Service; namespace App\Service;
use App\Entity\Business; use App\Entity\Business;
use App\Entity\HesabdariDoc;
use App\Entity\Plugin; use App\Entity\Plugin;
use App\Entity\PlugRepserviceOrder; use App\Entity\PluginProdect;
use App\Entity\User;
use App\Module\RemoteAddress;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
class PluginService class PluginService
@ -22,6 +19,13 @@ class PluginService
public function isActive(string $plugin, Business | string | null $bid = null ): bool public function isActive(string $plugin, Business | string | null $bid = null ): bool
{ {
// بررسی فعال بودن پلاگین به صورت پیش‌فرض
$pluginProduct = $this->em->getRepository(PluginProdect::class)->findOneBy(['code' => $plugin]);
if ($pluginProduct && $pluginProduct->isDefaultOn()) {
return true;
}
// اگر پلاگین به صورت پیش‌فرض فعال نباشد، بررسی می‌کنیم که آیا برای این کسب‌وکار فعال است یا خیر
if(is_string($bid)) if(is_string($bid))
$bid = $this->em->getRepository(Business::class)->find($bid); $bid = $this->em->getRepository(Business::class)->find($bid);
$ps = $this->em->getRepository(Plugin::class)->findBy([ $ps = $this->em->getRepository(Plugin::class)->findBy([

View file

@ -7,20 +7,26 @@ use Twig\Environment;
use Mpdf\Mpdf; use Mpdf\Mpdf;
use Mpdf\Config\ConfigVariables; use Mpdf\Config\ConfigVariables;
use Mpdf\Config\FontVariables; use Mpdf\Config\FontVariables;
use App\Service\PluginService;
class pdfMGR class pdfMGR
{ {
private $twig; private $twig;
private $pluginService;
public function __construct(Environment $twig) public function __construct(Environment $twig, PluginService $pluginService)
{ {
$this->twig = $twig; $this->twig = $twig;
$this->pluginService = $pluginService;
} }
public function generateTwig2PDF(PrinterQueue $printQueue, $configs = []): string public function generateTwig2PDF(PrinterQueue $printQueue, $configs = []): string
{ {
$template = $this->twig->load('pdf/footer.html.twig'); $template = $this->twig->load('pdf/footer.html.twig');
$footer = $template->render([]); $footer = $template->render([
'pluginService' => $this->pluginService,
'bid' => $printQueue->getBid()
]);
$size = $printQueue->getPaperSize() ?: 'A4-L'; $size = $printQueue->getPaperSize() ?: 'A4-L';
@ -61,8 +67,7 @@ class pdfMGR
$mpdf->SetAutoPageBreak(true); $mpdf->SetAutoPageBreak(true);
$mpdf->SetTitle('PDF Export'); $mpdf->SetTitle('PDF Export');
// به جای Output مستقیم، محتوا رو برگردونید return $mpdf->Output('', 'S');
return $mpdf->Output('', 'S'); // 'S' برای برگرداندن به صورت رشته
} }
public function generateTwig2PDFInvoiceType(PrinterQueue $printQueue, $configs = []): string public function generateTwig2PDFInvoiceType(PrinterQueue $printQueue, $configs = []): string

View file

@ -7,6 +7,7 @@ namespace App\Service;
use App\Entity\ChangeReport; use App\Entity\ChangeReport;
use App\Entity\Plugin; use App\Entity\Plugin;
use App\Entity\Settings; use App\Entity\Settings;
use App\Entity\PrintOptions;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
@ -118,4 +119,28 @@ class twigFunctions
return $this->request->getUri(); return $this->request->getUri();
} }
public function getFooterText(string $side, PluginService $pluginService, $bid): string
{
// اگر پلاگین accpro فعال نباشد، مقدار پیش‌فرض را برمی‌گرداند
if (!$pluginService->isActive('accpro', $bid)) {
return $side === 'left' ? $this->getStaticData('system', 'footerLeft') : $this->getStaticData('system', 'footerRight');
}
// دریافت تنظیمات چاپ
$printOptions = $this->em->getRepository(PrintOptions::class)->findOneBy(['bid' => $bid]);
if (!$printOptions) {
return $side === 'left' ? $this->getStaticData('system', 'footerLeft') : $this->getStaticData('system', 'footerRight');
}
// دریافت متن پانویس بر اساس سمت
$footerText = $side === 'left' ? $printOptions->getLeftFooter() : $printOptions->getRightFooter();
// اگر متن null یا خالی باشد، مقدار پیش‌فرض را برمی‌گرداند
if ($footerText === null || $footerText === '') {
return $side === 'left' ? $this->getStaticData('system', 'footerLeft') : $this->getStaticData('system', 'footerRight');
}
return $footerText;
}
} }

View file

@ -12,7 +12,7 @@
<thead> <thead>
<tr> <tr>
<td style="width:30%;text-align:center;"> <td style="width:30%;text-align:center;">
{{ twigFunctions.getStaticData('system', 'footerLeft') }} {{ twigFunctions.getFooterText('right', pluginService, bid) }}
</td> </td>
<td style="width:40%; text-align:center"> <td style="width:40%; text-align:center">
صفحه: صفحه:
@ -21,14 +21,14 @@
{nbpg} {nbpg}
</td> </td>
<td style="width:30%;text-align:center;"> <td style="width:30%;text-align:center;">
{{ twigFunctions.getStaticData('system', 'footerRight') }} {{ twigFunctions.getFooterText('left', pluginService, bid) }}
</td> </td>
</tr> </tr>
</thead> </thead>
</table> </table>
</div> </div>
</div> </div>
</body> </div>
</body> </body>
</html> </html>

View file

@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="fa" direction="rtl">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<head>
<style>
.center {
text-align: center;
}
.text-white {
color: white;
}
.stimol td,
.stimol th {
border: 1px solid black;
}
.item {
height: 30px;
}
</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">
<h3 class="">گزارش موجودی انبار</h3>
</td>
<td style="width:20%">
<h4>
<b>تاریخ:</b>
{{ "now"|date("Y/m/d H:i") }}</h4>
<br/>
<h4>
<b>انبار:</b>
{{ storeroom is defined and storeroom is not null ? storeroom.name : 'نامشخص' }}</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>
{{ storeroom is defined and storeroom is not null ? storeroom.name : 'نامشخص' }}
</p>
</td>
<td class="center">
<p>
<b>انباردار:</b>
{{ storeroom is defined and storeroom is not null ? storeroom.manager : 'نامشخص' }}
</p>
</td>
<td class="center">
<p>
<b>آدرس:</b>
{{ storeroom is defined and storeroom is not null ? storeroom.adr : 'نامشخص' }}
</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>
<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">{{ loop.index }}</td>
<td class="center item">{{ item.commodity.code }}</td>
<td class="center item">{{ item.commodity.cat.name }}</td>
<td class="center item">{{ item.commodity.name }}</td>
<td class="center item">{{ item.commodity.unit.name }}</td>
<td class="center item">{{ item.input }}</td>
<td class="center item">{{ item.output }}</td>
<td class="center item {% if item.existCount < 0 %}text-danger{% endif %}">
{{ item.existCount|abs }}
{% if item.existCount < 0 %}
(منفی)
{% endif %}
</td>
<td class="center item">{{ item.commodity.orderPoint }}</td>
<td class="center item">
{% if item.existCount < item.commodity.orderPoint %}
نیاز به شارژ انبار
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="width:100%;margin-top:0px;text-align:center;">
<table style="width:100%;">
<tbody>
<tr class="stimol">
<td class="item" style="width:70%;padding:1%">
<h4>
توضیحات:
<br>
تعداد کل کالاها: {{ items|length }}
</h4>
</td>
<td class="item" style="width:15%;padding:1%">
<h4>
چاپ شده توسط:
{{ app.user.fullname }}
</h4>
</td>
</tr>
</tbody>
</table>
</div>
<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>
<img src="{{ url('front_seal_file_get', {id: bid.id},)}}" width="160"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View file

@ -385,7 +385,7 @@ export default defineComponent({
async fetchData() { async fetchData() {
this.loading = true this.loading = true
try { try {
const response = await axios.post('/api/commodity/list', { const response = await axios.post('/api/commodity/list/search', {
page: this.currentPage, page: this.currentPage,
itemsPerPage: this.itemsPerPage, itemsPerPage: this.itemsPerPage,
search: this.searchQuery, search: this.searchQuery,

View file

@ -30,45 +30,63 @@
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-form ref="form" @submit.prevent="submitForm" v-model="valid"> <v-form ref="form" @submit.prevent="submitForm" v-model="valid" :disabled="loading">
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<Hpersonsearch v-model="form.personId" label="شخص" :rules="[v => !!v || 'شخص الزامی است']" required> <Hpersonsearch v-model="form.personId" label="شخص" :rules="[v => !!v || 'شخص الزامی است']" required :disabled="loading">
</Hpersonsearch> </Hpersonsearch>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="form.chequeNumber" label="شماره چک" :rules="[v => !!v || 'شماره چک الزامی است']" <v-text-field v-model="form.chequeNumber" label="شماره چک" :rules="[v => !!v || 'شماره چک الزامی است']"
required></v-text-field> required :disabled="loading"></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="form.bankoncheque" label="نام بانک" :rules="[v => !!v || 'نام بانک الزامی است']" <v-text-field v-model="form.bankoncheque" label="نام بانک" :rules="[v => !!v || 'نام بانک الزامی است']"
required></v-text-field> required :disabled="loading"></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<Hnumberinput v-model="form.amount" label="مبلغ" :rules="[v => !!v || 'مبلغ الزامی است']" <Hnumberinput v-model="form.amount" label="مبلغ" :rules="[v => !!v || 'مبلغ الزامی است']"
required></Hnumberinput> required :disabled="loading"></Hnumberinput>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="form.sayadNumber" label="شماره صیاد" :rules="[v => !!v || 'شماره صیاد الزامی است']" <v-text-field v-model="form.sayadNumber" label="شماره صیاد" :rules="[v => !!v || 'شماره صیاد الزامی است']"
required></v-text-field> required :disabled="loading"></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<Hdatepicker v-model="form.dueDate" label="تاریخ سررسید" :rules="[v => !!v || 'تاریخ سررسید الزامی است']" <Hdatepicker v-model="form.dueDate" label="تاریخ سررسید" :rules="[v => !!v || 'تاریخ سررسید الزامی است']"
required></Hdatepicker> required :disabled="loading"></Hdatepicker>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-textarea v-model="form.description" label="توضیحات" rows="3"></v-textarea> <v-textarea v-model="form.description" label="توضیحات" rows="3" :disabled="loading"></v-textarea>
</v-col> </v-col>
</v-row> </v-row>
</v-form> </v-form>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
location="bottom"
>
{{ snackbar.text }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar.show = false"
>
بستن
</v-btn>
</template>
</v-snackbar>
</template> </template>
<script> <script>
@ -86,6 +104,12 @@ export default {
return { return {
loading: false, loading: false,
valid: false, valid: false,
isSubmitting: false,
snackbar: {
show: false,
text: '',
color: 'success'
},
form: { form: {
chequeNumber: '', chequeNumber: '',
bankoncheque: '', bankoncheque: '',
@ -101,6 +125,8 @@ export default {
methods: { methods: {
async submitForm() { async submitForm() {
if (this.isSubmitting) return;
const { valid } = await this.$refs.form.validate() const { valid } = await this.$refs.form.validate()
if (!valid) { if (!valid) {
@ -108,9 +134,12 @@ export default {
} }
try { try {
this.loading = true this.loading = true;
this.isSubmitting = true;
// ذخیره تنظیمات در localStorage // ذخیره تنظیمات در localStorage
localStorage.setItem('chequeSendSms', this.form.sendSms) localStorage.setItem('chequeSendSms', this.form.sendSms)
if (this.$route.params.id) { if (this.$route.params.id) {
// ویرایش چک // ویرایش چک
await axios.put(`/api/cheque/modify/input/${this.$route.params.id}`, { await axios.put(`/api/cheque/modify/input/${this.$route.params.id}`, {
@ -123,6 +152,7 @@ export default {
bankoncheque: this.form.bankoncheque, bankoncheque: this.form.bankoncheque,
sendSms: this.form.sendSms sendSms: this.form.sendSms
}) })
this.showSnackbar('چک با موفقیت ویرایش شد', 'success')
} else { } else {
// ثبت چک جدید // ثبت چک جدید
await axios.post('/api/cheque/modify/input', { await axios.post('/api/cheque/modify/input', {
@ -135,13 +165,25 @@ export default {
bankoncheque: this.form.bankoncheque, bankoncheque: this.form.bankoncheque,
sendSms: this.form.sendSms sendSms: this.form.sendSms
}) })
this.showSnackbar('چک با موفقیت ثبت شد', 'success')
} }
setTimeout(() => {
this.$router.push('/acc/cheque/list') this.$router.push('/acc/cheque/list')
}, 1500)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
this.showSnackbar('خطا در ثبت/ویرایش چک', 'error')
} finally { } finally {
this.loading = false this.loading = false
this.isSubmitting = false
} }
},
showSnackbar(text, color) {
this.snackbar.text = text
this.snackbar.color = color
this.snackbar.show = true
} }
}, },

View file

@ -19,45 +19,63 @@
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-form ref="form" @submit.prevent="submitForm"> <v-form ref="form" @submit.prevent="submitForm" :disabled="loading">
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<Hpersonsearch v-model="form.personId" label="شخص گیرنده" :rules="[v => !!v || 'شخص گیرنده الزامی است']" required> <Hpersonsearch v-model="form.personId" label="شخص گیرنده" :rules="[v => !!v || 'شخص گیرنده الزامی است']" required :disabled="loading">
</Hpersonsearch> </Hpersonsearch>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="form.chequeNumber" label="شماره چک" :rules="[v => !!v || 'شماره چک الزامی است']" <v-text-field v-model="form.chequeNumber" label="شماره چک" :rules="[v => !!v || 'شماره چک الزامی است']"
required></v-text-field> required :disabled="loading"></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="form.bankoncheque" label="نام بانک" :rules="[v => !!v || 'نام بانک الزامی است']" <v-text-field v-model="form.bankoncheque" label="نام بانک" :rules="[v => !!v || 'نام بانک الزامی است']"
required></v-text-field> required :disabled="loading"></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<Hnumberinput v-model="form.amount" label="مبلغ" :rules="[v => !!v || 'مبلغ الزامی است']" <Hnumberinput v-model="form.amount" label="مبلغ" :rules="[v => !!v || 'مبلغ الزامی است']"
required></Hnumberinput> required :disabled="loading"></Hnumberinput>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="form.sayadNumber" label="شماره صیاد" :rules="[v => !!v || 'شماره صیاد الزامی است']" <v-text-field v-model="form.sayadNumber" label="شماره صیاد" :rules="[v => !!v || 'شماره صیاد الزامی است']"
required></v-text-field> required :disabled="loading"></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<Hdatepicker v-model="form.dueDate" label="تاریخ سررسید" :rules="[v => !!v || 'تاریخ سررسید الزامی است']" <Hdatepicker v-model="form.dueDate" label="تاریخ سررسید" :rules="[v => !!v || 'تاریخ سررسید الزامی است']"
required format="YYYY/MM/DD"></Hdatepicker> required format="YYYY/MM/DD" :disabled="loading"></Hdatepicker>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-textarea v-model="form.description" label="توضیحات" rows="3"></v-textarea> <v-textarea v-model="form.description" label="توضیحات" rows="3" :disabled="loading"></v-textarea>
</v-col> </v-col>
</v-row> </v-row>
</v-form> </v-form>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
location="bottom"
>
{{ snackbar.text }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar.show = false"
>
بستن
</v-btn>
</template>
</v-snackbar>
</template> </template>
<script> <script>
@ -74,6 +92,12 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
isSubmitting: false,
snackbar: {
show: false,
text: '',
color: 'success'
},
form: { form: {
chequeNumber: '', chequeNumber: '',
bankoncheque: '', bankoncheque: '',
@ -88,19 +112,19 @@ export default {
methods: { methods: {
async submitForm() { async submitForm() {
if (this.isSubmitting) return;
const { valid } = await this.$refs.form.validate() const { valid } = await this.$refs.form.validate()
if (!valid) { if (!valid) {
await Swal.fire({ this.showSnackbar('لطفا تمام فیلدهای الزامی را پر کنید', 'warning')
text: 'لطفا تمام فیلدهای الزامی را پر کنید',
icon: 'warning',
confirmButtonText: 'قبول'
})
return return
} }
try { try {
this.loading = true this.loading = true;
this.isSubmitting = true;
if (this.$route.params.id) { if (this.$route.params.id) {
// ویرایش واگذاری چک // ویرایش واگذاری چک
await axios.put(`/api/cheque/modify/output/${this.$route.params.id}`, { await axios.put(`/api/cheque/modify/output/${this.$route.params.id}`, {
@ -112,6 +136,7 @@ export default {
description: this.form.description, description: this.form.description,
bankoncheque: this.form.bankoncheque bankoncheque: this.form.bankoncheque
}) })
this.showSnackbar('واگذاری چک با موفقیت ویرایش شد', 'success')
} else { } else {
// ثبت واگذاری چک جدید // ثبت واگذاری چک جدید
await axios.post('/api/cheque/modify/output', { await axios.post('/api/cheque/modify/output', {
@ -123,18 +148,25 @@ export default {
description: this.form.description, description: this.form.description,
bankoncheque: this.form.bankoncheque bankoncheque: this.form.bankoncheque
}) })
this.showSnackbar('واگذاری چک با موفقیت ثبت شد', 'success')
} }
setTimeout(() => {
this.$router.push('/acc/cheque/list') this.$router.push('/acc/cheque/list')
}, 1500)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
await Swal.fire({ this.showSnackbar('خطا در ثبت/ویرایش واگذاری چک', 'error')
text: 'خطا در ثبت/ویرایش چک',
icon: 'error',
confirmButtonText: 'قبول'
})
} finally { } finally {
this.loading = false this.loading = false
this.isSubmitting = false
} }
},
showSnackbar(text, color) {
this.snackbar.text = text
this.snackbar.color = color
this.snackbar.show = true
} }
}, },

View file

@ -15,22 +15,25 @@
</v-btn> </v-btn>
<template v-slot:extension> <template v-slot:extension>
<v-tabs color="primary" class="bg-light" grow v-model="tabs"> <v-tabs color="primary" class="bg-light" grow v-model="tabs">
<v-tab value="0"> <v-tab v-if="isPluginActive('accpro')" value="0">
{{ $t('drawer.sell') }} تنظیم سراسری
</v-tab> </v-tab>
<v-tab value="1"> <v-tab value="1">
{{ $t('drawer.sell') }}
</v-tab>
<v-tab value="2">
{{ $t('drawer.buy') }} {{ $t('drawer.buy') }}
</v-tab> </v-tab>
<v-tab v-if="isPluginActive('accpro')" value="2"> <v-tab v-if="isPluginActive('accpro')" value="3">
{{ $t('drawer.rfbuy_invoices') }} {{ $t('drawer.rfbuy_invoices') }}
</v-tab> </v-tab>
<v-tab v-if="isPluginActive('accpro')" value="3"> <v-tab v-if="isPluginActive('accpro')" value="4">
{{ $t('drawer.rfsell_invoices') }} {{ $t('drawer.rfsell_invoices') }}
</v-tab> </v-tab>
<v-tab value="4"> <v-tab value="5">
{{ $t('drawer.fast_sell') }} {{ $t('drawer.fast_sell') }}
</v-tab> </v-tab>
<v-tab v-if="isPluginActive('repservice')" value="5"> <v-tab v-if="isPluginActive('repservice')" value="6">
{{ $t('drawer.repservice') }} {{ $t('drawer.repservice') }}
</v-tab> </v-tab>
</v-tabs> </v-tabs>
@ -40,6 +43,26 @@
<v-col> <v-col>
<v-tabs-window v-model="tabs"> <v-tabs-window v-model="tabs">
<v-tabs-window-item value="0"> <v-tabs-window-item value="0">
<v-card>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="settings.global.leftFooter" label="پانویس سمت چپ"
placeholder="متن پانویس سمت چپ اسناد"
hint="در صورت خالی بودن، مقدار پیش‌فرض نمایش داده می‌شود"
persistent-hint></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="settings.global.rightFooter" label="پانویس سمت راست"
placeholder="متن پانویس سمت راست اسناد"
hint="در صورت خالی بودن، مقدار پیش‌فرض نمایش داده می‌شود"
persistent-hint></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-tabs-window-item>
<v-tabs-window-item value="1">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-row> <v-row>
@ -75,7 +98,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="1"> <v-tabs-window-item value="2">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-row> <v-row>
@ -111,7 +134,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="2"> <v-tabs-window-item value="3">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-row> <v-row>
@ -147,7 +170,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="3"> <v-tabs-window-item value="4">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-row> <v-row>
@ -183,7 +206,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="4"> <v-tabs-window-item value="5">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-row> <v-row>
@ -204,7 +227,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="5"> <v-tabs-window-item value="6">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-row> <v-row>
@ -239,7 +262,7 @@ export default {
}, },
data: () => ({ data: () => ({
loading: ref(false), loading: ref(false),
tabs: 0, tabs: ref(1),
plugins: [], plugins: [],
paperOptions: [ paperOptions: [
{ title: 'A4 افقی', value: 'A4-L' }, { title: 'A4 افقی', value: 'A4-L' },
@ -248,6 +271,10 @@ export default {
{ title: 'A5 عمودی', value: 'A5' } { title: 'A5 عمودی', value: 'A5' }
], ],
settings: { settings: {
global: {
leftFooter: '',
rightFooter: ''
},
sell: { sell: {
pays: true, pays: true,
note: true, note: true,
@ -299,6 +326,9 @@ export default {
isPluginActive(plugName) { isPluginActive(plugName) {
return this.plugins[plugName] !== undefined; return this.plugins[plugName] !== undefined;
}, },
getStaticData(type, key) {
return this.$store.getters.getStaticData(type, key);
},
submit() { submit() {
this.loading = true; this.loading = true;
axios.post('/api/printers/options/save', this.settings).then((response) => { axios.post('/api/printers/options/save', this.settings).then((response) => {

View file

@ -1,162 +1,207 @@
<script lang="ts"> <script lang="ts">
import {defineComponent,ref} from 'vue' import { defineComponent, ref, watch, onMounted } from 'vue'
import axios from "axios"; import axios from "axios";
import { getApiUrl } from '@/hesabixConfig';
export default defineComponent({ export default defineComponent({
name: "checkByStoreroom", name: "checkByStoreroom",
data:()=>{return{ setup() {
storerooms:[], const storerooms = ref([])
storeroom:null, const storeroom = ref(null)
searchValue: '', const searchValue = ref('')
loading : ref(true), const loading = ref(true)
items:[], const items = ref([])
orgItems:[], const orgItems = ref([])
headers: [ const showZeroTransactions = ref(true)
{ text: "کد", value: "commodity.code" }, const headers = [
{ text: "دسته بندی", value: "commodity.cat.name", sortable: true}, { title: "کد", key: "commodity.code" },
{ text: "نام", value: "commodity.name", sortable: true}, { title: "دسته بندی", key: "commodity.cat.name", sortable: true },
{ text: "واحد", value: "commodity.unit.name", sortable: true}, { title: "نام", key: "commodity.name", sortable: true },
{ text: "ورودی", value: "input", sortable: true}, { title: "واحد", key: "commodity.unit.name", sortable: true },
{ text: "خروجی", value: "output", sortable: true}, { title: "ورودی", key: "input", sortable: true },
{ text: "موجودی انبار", value: "existCount"}, { title: "خروجی", key: "output", sortable: true },
{ text: "نقطه سفارش", value: "commodity.orderPoint"}, { title: "موجودی انبار", key: "existCount" },
{ text: "وضعیت", value: "operation"}, { title: "نقطه سفارش", key: "commodity.orderPoint" },
{ title: "وضعیت", key: "operation" },
] ]
}},
watch:{ const loadData = async () => {
'searchValue'(newValue, oldValue) { loading.value = true
this.searchTable(); try {
const response = await axios.post('/api/storeroom/list')
storerooms.value = response.data.data.map((item: any) => ({
...item,
name: `${item.name} انباردار : ${item.manager}`
}))
} finally {
loading.value = false
} }
},
methods: {
loadData(){
this.loading = true;
axios.post('/api/storeroom/list')
.then((response)=>{
this.storerooms = response.data.data;
this.storerooms.forEach((element)=>{
element.name = element.name + ' انباردار : ' + element.manager
});
this.loading = false;
});
},
loadStoreItems(){
this.loading = true;
axios.post('/api/storeroom/commodity/list/' + this.storeroom.id)
.then((response)=>{
this.items = response.data;
this.orgItems = response.data;
this.loading = false;
});
},
searchTable() {
this.loading = true;
let calcItems = [];
let isAll = false;
if(this.searchValue == ''){isAll = true;}
if (isAll) {
this.items = this.orgItems;
} }
else {
this.orgItems.forEach((item) => { const loadStoreItems = async () => {
let addItem = false; if (!storeroom.value) return
if(item.commodity.name.includes(this.searchValue)){ loading.value = true
addItem = true try {
const response = await axios.post('/api/storeroom/commodity/list/' + storeroom.value)
orgItems.value = response.data
filterItems()
} finally {
loading.value = false
} }
else if(item.commodity.cat.name.includes(this.searchValue)){
addItem = true
} }
else if(item.commodity.unit.name.includes(this.searchValue)){
addItem = true const filterItems = () => {
let filteredItems = [...orgItems.value]
if (!showZeroTransactions.value) {
filteredItems = filteredItems.filter((item: any) => {
return item.input !== 0 || item.output !== 0 || item.existCount !== 0
})
} }
if(addItem){
calcItems.push(item); if (searchValue.value) {
filteredItems = filteredItems.filter((item: any) => {
const commodity = item?.commodity ?? {}
const cat = commodity?.cat ?? {}
const unit = commodity?.unit ?? {}
return (
(commodity.name || '').includes(searchValue.value) ||
(cat.name || '').includes(searchValue.value) ||
(unit.name || '').includes(searchValue.value)
)
})
} }
});
this.items = calcItems; items.value = filteredItems
}
const print = () => {
if (!storeroom.value) {
alert('لطفا ابتدا انبار را انتخاب کنید')
return
}
loading.value = true
axios.post('/api/storeroom/exist/print', {
storeroom: storeroom.value,
items: items.value,
}).then((res) => {
if (res.data.id) {
window.open(getApiUrl() + `/front/print/${res.data.id}`, '_blank')
}
}).catch((error) => {
console.error('خطا در چاپ:', error)
alert('خطا در چاپ گزارش')
}).finally(() => {
loading.value = false
})
}
watch(searchValue, filterItems)
watch(showZeroTransactions, filterItems)
onMounted(loadData)
return {
storerooms,
storeroom,
searchValue,
loading,
items,
headers,
loadStoreItems,
showZeroTransactions,
print
} }
this.loading = false;
},
},
beforeMount() {
this.loadData();
} }
}) })
</script> </script>
<template> <template>
<div class="block block-content-full "> <v-toolbar color="toolbar" title="موجودی کالا">
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1"> <template v-slot:prepend>
<h3 class="block-title text-primary-dark"> <v-tooltip :text="$t('dialog.back')" location="bottom">
<button @click="$router.back()" type="button" class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning"> <template v-slot:activator="{ props }">
<i class="fa fw-bold fa-arrow-right"></i> <v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
</button>
<i class="mx-2 fa fa-boxes-stacked"></i>
موجودی کالا </h3>
<div class="block-options">
<button class="btn btn-sm btn-primary mx-2" onclick="document.getElementById('hide-on-print').classList.add('d-none');Dashmix.helpers('dm-print');" type="button">
<i class="si si-printer me-1"></i>
<span class="d-none d-sm-inline-block">چاپ</span>
</button>
</div>
</div>
<div class="block-content pt-1 pb-3 dm-print">
<div class="row">
<div class="col-sm-12 col-md-12 px-0 mb-2">
<div class="form-control">
<label class="form-label">
انبار :
<span class="text-muted">برای مشاهده موجودی ابتدا انبار را انتخاب نمایید.</span>
</label>
<v-cob
dir="rtl"
:options="storerooms"
label="name"
v-model="storeroom"
@option:selected="loadStoreItems()"
>
<template #no-options="{ search, searching, loading }">
نتیجهای یافت نشد!
</template> </template>
</v-cob> </v-tooltip>
</div> </template>
</div> <v-spacer></v-spacer>
<div class="col-sm-12 col-md-12 m-0 p-0"> <v-tooltip text="نمایش/عدم نمایش کالاهای بدون تراکنش" location="bottom">
<div class="mb-1"> <template v-slot:activator="{ props }">
<div class="input-group input-group-sm"> <v-switch
<span class="input-group-text"><i class="fa fa-search"></i></span> v-bind="props"
<input v-model="searchValue" class="form-control" type="text" placeholder="جست و جو ..."> v-model="showZeroTransactions"
</div> class="mx-4"
</div> hide-details
<EasyDataTable table-class-name="customize-table" color="primary"
multi-sort ></v-switch>
show-index </template>
alternating </v-tooltip>
<v-tooltip :text="$t('dialog.print')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon @click="print()" color="primary">
<v-icon>mdi-printer</v-icon>
</v-btn>
</template>
</v-tooltip>
</v-toolbar>
<v-container fluid>
<v-row>
<v-col cols="12">
<v-select
v-model="storeroom"
:items="storerooms"
item-title="name"
item-value="id"
label="انبار"
hint="برای مشاهده موجودی ابتدا انبار را انتخاب نمایید."
persistent-hint
@update:model-value="loadStoreItems"
clearable
></v-select>
</v-col>
<v-col cols="12" class="my-0 py-0">
<v-text-field
v-model="searchValue"
prepend-inner-icon="mdi-magnify"
label="جستجو"
variant="outlined"
density="compact"
hide-details
></v-text-field>
</v-col>
<v-col cols="12">
<v-data-table
:headers="headers" :headers="headers"
:items="items" :items="items"
theme-color="#1d90ff"
header-text-direction="center"
body-text-direction="center"
rowsPerPageMessage="تعداد سطر"
emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
rowsOfPageSeparatorMessage="از"
:loading="loading" :loading="loading"
:search="searchValue"
multi-sort
density="comfortable"
class="elevation-1"
:header-props="{ class: 'custom-header' }"
> >
<template #item-existCount="{ existCount }"> <template v-slot:item.existCount="{ item }">
<b>{{Math.abs(existCount)}}</b> <b>{{ Math.abs(item.existCount) }}</b>
<span v-if="parseInt(existCount) < 0" class="text-danger"> (منفی) </span> <span v-if="parseInt(item.existCount) < 0" class="text-error"> (منفی) </span>
</template> </template>
<template #item-operation="{ existCount, commodity}">
<span v-if="parseInt(existCount) < parseInt(commodity.orderPoint)" class="text-danger"> نیاز به شارژ انبار </span> <template v-slot:item.operation="{ item }">
<span v-if="parseInt(item.existCount) < parseInt(item.commodity.orderPoint)" class="text-error">
نیاز به شارژ انبار
</span>
</template> </template>
</EasyDataTable> </v-data-table>
</div> </v-col>
</div> </v-row>
</div> </v-container>
</div>
</template> </template>
<style scoped> <style scoped>
.text-error {
color: rgb(var(--v-theme-error));
}
</style> </style>