update for two-step system

This commit is contained in:
Gloomy 2025-08-20 08:11:52 +00:00
parent 8d91bcd4ea
commit 28ad56d972
5 changed files with 63 additions and 85 deletions

View file

@ -50,7 +50,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']); return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']);
} }
$canApprove = $this->canUserApproveStoreroomTicket($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'storeroom');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']);
} }
@ -112,7 +112,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']); return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']);
} }
$canApprove = $this->canUserApproveStoreroomTicket($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'storeroom');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']);
} }
@ -174,7 +174,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']); return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']);
} }
$canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'sell');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
} }
@ -240,7 +240,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
} }
$canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'sell');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']);
} }
@ -333,7 +333,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']); return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']);
} }
$canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'sell');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
} }
@ -408,7 +408,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']); return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']);
} }
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'buy');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
} }
@ -461,7 +461,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
} }
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'buy');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']);
} }
@ -489,7 +489,7 @@ class ApprovalController extends AbstractController
$entityManager->persist($document); $entityManager->persist($document);
} }
$entityManager->flush(); $entityManager->flush();
$logService->insert( $logService->insert(
@ -542,7 +542,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']); return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']);
} }
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'buy');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
} }
@ -595,7 +595,7 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
} }
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings); $canApprove = $this->canUserApproveDocument($user, $businessSettings, 'buy');
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']);
} }
@ -623,7 +623,7 @@ class ApprovalController extends AbstractController
$entityManager->persist($document); $entityManager->persist($document);
} }
$entityManager->flush(); $entityManager->flush();
$logService->insert( $logService->insert(
@ -646,67 +646,29 @@ class ApprovalController extends AbstractController
} }
} }
private function canUserApproveDocument(User $user, Business $business, HesabdariDoc $document): bool private function canUserApproveDocument(User $user, Business $business, string $documentType): bool
{ {
if ($user->getEmail() === $business->getOwner()->getEmail()) { if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true; return true;
} }
$documentType = $this->getDocumentType($document); $approversMap = [
'sell' => 'getApproverSellInvoice',
'buy' => 'getApproverBuyInvoice',
'storeroom' => 'getApproverWarehouseTransfer',
'rfsell' => 'getApproverReturnSell',
'rfbuy' => 'getApproverReturnBuy',
'sell_receive' => 'getApproverReceiveFromPersons',
'buy_send' => 'getApproverPayToPersons',
'hesabdari' => 'getApproverAccountingDocs',
'transfer' => 'getApproverBankTransfers',
];
switch ($documentType) { if (!isset($approversMap[$documentType])) {
case 'invoice': return false;
return $business->getApproverSellInvoice() === $user->getEmail();
case 'warehouse':
return $business->getApproverWarehouseTransfer() === $user->getEmail();
default:
return false;
}
}
private function getDocumentType(HesabdariDoc $document): string
{
$type = $document->getType();
if (strpos($type, 'sell') !== false || strpos($type, 'invoice') !== false) {
return 'invoice';
} }
if (strpos($type, 'warehouse') !== false || strpos($type, 'storeroom') !== false) { $method = $approversMap[$documentType];
return 'warehouse'; return $business->$method() === $user->getEmail();
}
if (strpos($type, 'payment') !== false || strpos($type, 'receipt') !== false || strpos($type, 'hesabdari') !== false) {
return 'financial';
}
return 'unknown';
}
private function canUserApproveStoreroomTicket(User $user, Business $business): bool
{
if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true;
}
return $business->getApproverWarehouseTransfer() === $user->getEmail();
}
private function canUserApproveSalesInvoice(User $user, Business $business): bool
{
if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true;
}
return $business->getApproverSellInvoice() === $user->getEmail();
}
private function canUserApproveBuyInvoice(User $user, Business $business): bool
{
if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true;
}
return $business->getApproverBuyInvoice() === $user->getEmail();
} }
} }

View file

@ -45,7 +45,7 @@ class SellController extends AbstractController
if (!$acc) if (!$acc)
throw $this->createAccessDeniedException(); throw $this->createAccessDeniedException();
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
'bid' => $acc['bid'], 'bid' => $acc['bid'],
'code' => $code, 'code' => $code,
'money' => $acc['money'] 'money' => $acc['money']
@ -1431,7 +1431,7 @@ class SellController extends AbstractController
throw $this->createAccessDeniedException(); throw $this->createAccessDeniedException();
} }
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
'bid' => $acc['bid'], 'bid' => $acc['bid'],
'year' => $acc['year'], 'year' => $acc['year'],
'code' => $id, 'code' => $id,

View file

@ -98,13 +98,13 @@ class HesabdariDocRepository extends ServiceEntityRepository
{ {
return $this->createQueryBuilder('h') return $this->createQueryBuilder('h')
->andWhere('h.id = :id') ->andWhere('h.id = :id')
->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))') ->andWhere('h.isApproved = 1')
->setParameter('id', $id) ->setParameter('id', $id)
->getQuery() ->getQuery()
->getOneOrNullResult(); ->getOneOrNullResult();
} }
public function findOneBy(array $criteria, array $orderBy = null): ?object public function findOneBy(array $criteria, ?array $orderBy = null): ?object
{ {
$qb = $this->createQueryBuilder('h'); $qb = $this->createQueryBuilder('h');
@ -116,7 +116,7 @@ class HesabdariDocRepository extends ServiceEntityRepository
} }
} }
$qb->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))'); $qb->andWhere('h.isApproved = 1');
if ($orderBy) { if ($orderBy) {
foreach ($orderBy as $field => $direction) { foreach ($orderBy as $field => $direction) {
@ -128,7 +128,7 @@ class HesabdariDocRepository extends ServiceEntityRepository
} }
//include preview //include preview
public function findOneByIncludePreview(array $criteria, array $orderBy = null): ?object public function findOneByIncludePreview(array $criteria, ?array $orderBy = null): ?object
{ {
$qb = $this->createQueryBuilder('h'); $qb = $this->createQueryBuilder('h');
@ -149,7 +149,7 @@ class HesabdariDocRepository extends ServiceEntityRepository
return $qb->getQuery()->getOneOrNullResult(); return $qb->getQuery()->getOneOrNullResult();
} }
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{ {
$qb = $this->createQueryBuilder('h'); $qb = $this->createQueryBuilder('h');
@ -161,7 +161,7 @@ class HesabdariDocRepository extends ServiceEntityRepository
} }
} }
$qb->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))'); $qb->andWhere('h.isApproved = 1');
if ($orderBy) { if ($orderBy) {
foreach ($orderBy as $field => $direction) { foreach ($orderBy as $field => $direction) {
@ -183,13 +183,13 @@ class HesabdariDocRepository extends ServiceEntityRepository
public function findAll(): array public function findAll(): array
{ {
return $this->createQueryBuilder('h') return $this->createQueryBuilder('h')
->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))') ->andWhere('h.isApproved = 1')
->getQuery() ->getQuery()
->getResult(); ->getResult();
} }
//include preview //include preview
public function findByIncludePreview(array $criteria, array $orderBy = null, $limit = null, $offset = null): array public function findByIncludePreview(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{ {
$qb = $this->createQueryBuilder('h'); $qb = $this->createQueryBuilder('h');

View file

@ -129,7 +129,7 @@
<v-btn variant="text" size="small" color="error" icon="mdi-menu" v-bind="props" /> <v-btn variant="text" size="small" color="error" icon="mdi-menu" v-bind="props" />
</template> </template>
<v-list> <v-list>
<v-list-item class="text-dark" :title="$t('dialog.accounting_doc')" <v-list-item v-if="currentTab === 'approved' && item.isApproved" class="text-dark" :title="$t('dialog.accounting_doc')"
:to="'/acc/accounting/view/' + item.code"> :to="'/acc/accounting/view/' + item.code">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-file"></v-icon> <v-icon color="green-darken-4" icon="mdi-file"></v-icon>
@ -140,7 +140,7 @@
<v-icon color="green-darken-4" icon="mdi-eye"></v-icon> <v-icon color="green-darken-4" icon="mdi-eye"></v-icon>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item class="text-dark" :title="$t('dialog.export_pdf')" <v-list-item v-if="currentTab === 'approved' && item.isApproved" class="text-dark" :title="$t('dialog.export_pdf')"
@click="printOptions.selectedPrintCode = item.code; modal = true;"> @click="printOptions.selectedPrintCode = item.code; modal = true;">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon icon="mdi-file-pdf-box"></v-icon> <v-icon icon="mdi-file-pdf-box"></v-icon>
@ -651,11 +651,15 @@ export default defineComponent({
async approveInvoice(code) { async approveInvoice(code) {
try { try {
this.loading = true; this.loading = true;
await axios.post(`/api/approval/approve/sales/${code}`); const response = await axios.post(`/api/approval/approve/sales/${code}`);
await this.loadData(); await this.loadData();
Swal.fire({ text: 'فاکتور تایید شد', icon: 'success', confirmButtonText: 'قبول' }); if (response.data.success) {
Swal.fire({ text: 'فاکتور تایید شد', icon: 'success', confirmButtonText: 'قبول' });
} else {
Swal.fire({ text: response.data.message, icon: 'error', confirmButtonText: 'قبول' });
}
} catch (error) { } catch (error) {
Swal.fire({ text: 'خطا در تایید فاکتور: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' }); Swal.fire({ text: 'خطا در تایید فاکتور: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' });
} finally { } finally {
@ -668,11 +672,15 @@ export default defineComponent({
async unapproveInvoice(code) { async unapproveInvoice(code) {
try { try {
this.loading = true; this.loading = true;
await axios.post(`/api/approval/unapprove/sales/${code}`); const response = await axios.post(`/api/approval/unapprove/sales/${code}`);
await this.loadData(); await this.loadData();
Swal.fire({ text: 'تایید فاکتور لغو شد', icon: 'success', confirmButtonText: 'قبول' }); if (response.data.success) {
Swal.fire({ text: 'تایید فاکتور لغو شد', icon: 'success', confirmButtonText: 'قبول' });
} else {
Swal.fire({ text: response.data.message, icon: 'error', confirmButtonText: 'قبول' });
}
} catch (error) { } catch (error) {
Swal.fire({ text: 'خطا در لغو تایید فاکتور: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' }); Swal.fire({ text: 'خطا در لغو تایید فاکتور: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' });
} finally { } finally {

View file

@ -566,11 +566,15 @@ const canShowUnapproveButton = (item: Ticket) => {
const approveTicket = async (code: string) => { const approveTicket = async (code: string) => {
try { try {
loading.value = true; loading.value = true;
await axios.post(`/api/approval/approve/storeroom/${code}`); const response = await axios.post(`/api/approval/approve/storeroom/${code}`);
await loadData(); await loadData();
snackbar.value = { show: true, message: 'حواله تایید شد', color: 'success' }; if (response.data.success) {
snackbar.value = { show: true, message: 'حواله تایید شد', color: 'success' };
} else {
snackbar.value = { show: true, message: response.data.message, color: 'error' };
}
} catch (error: any) { } catch (error: any) {
snackbar.value = { show: true, message: 'خطا در تایید حواله: ' + (error.response?.data?.message || error.message), color: 'error' }; snackbar.value = { show: true, message: 'خطا در تایید حواله: ' + (error.response?.data?.message || error.message), color: 'error' };
} finally { } finally {
@ -581,11 +585,15 @@ const approveTicket = async (code: string) => {
const unapproveTicket = async (code: string) => { const unapproveTicket = async (code: string) => {
try { try {
loading.value = true; loading.value = true;
await axios.post(`/api/approval/unapprove/storeroom/${code}`); const response = await axios.post(`/api/approval/unapprove/storeroom/${code}`);
await loadData(); await loadData();
snackbar.value = { show: true, message: 'حواله لغو تایید شد', color: 'success' }; if (response.data.success) {
snackbar.value = { show: true, message: 'حواله لغو تایید شد', color: 'success' };
} else {
snackbar.value = { show: true, message: response.data.message, color: 'error' };
}
} catch (error: any) { } catch (error: any) {
snackbar.value = { show: true, message: 'خطا در لغو تایید حواله: ' + (error.response?.data?.message || error.message), color: 'error' }; snackbar.value = { show: true, message: 'خطا در لغو تایید حواله: ' + (error.response?.data?.message || error.message), color: 'error' };
} finally { } finally {