update for two-step system

This commit is contained in:
Gloomy 2025-08-19 14:16:17 +00:00
parent e775de8f77
commit f3517d55d6
6 changed files with 748 additions and 433 deletions

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250819120657 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE hesabdari_row ADD is_preview TINYINT(1) DEFAULT NULL, ADD is_approved TINYINT(1) DEFAULT NULL, ADD approved_by_id INT DEFAULT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE hesabdari_row ADD CONSTRAINT FK_83B2C6EC2D234F6A FOREIGN KEY (approved_by_id) REFERENCES user (id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_83B2C6EC2D234F6A ON hesabdari_row (approved_by_id)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE hesabdari_row DROP FOREIGN KEY FK_83B2C6EC2D234F6A
SQL);
$this->addSql(<<<'SQL'
DROP INDEX IDX_83B2C6EC2D234F6A ON hesabdari_row
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE hesabdari_row DROP is_preview, DROP is_approved, DROP approved_by_id
SQL);
}
}

View file

@ -20,7 +20,6 @@ use Symfony\Component\Security\Http\Attribute\CurrentUser;
class ApprovalController extends AbstractController class ApprovalController extends AbstractController
{ {
// تأیید حواله انبار
#[Route('/api/approval/approve/storeroom/{ticketCode}', name: 'api_approval_approve_storeroom', methods: ['POST'])] #[Route('/api/approval/approve/storeroom/{ticketCode}', name: 'api_approval_approve_storeroom', methods: ['POST'])]
public function approveStoreroomTicket( public function approveStoreroomTicket(
$ticketCode, $ticketCode,
@ -30,7 +29,6 @@ class ApprovalController extends AbstractController
EntityManagerInterface $entityManager EntityManagerInterface $entityManager
): Response { ): Response {
try { try {
// بررسی دسترسی کاربر
$acc = $access->hasRole('settings'); $acc = $access->hasRole('settings');
if (!$acc) { if (!$acc) {
throw $this->createAccessDeniedException(); throw $this->createAccessDeniedException();
@ -39,12 +37,10 @@ class ApprovalController extends AbstractController
$business = $acc['bid']; $business = $acc['bid'];
$businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId());
// بررسی اینکه آیا تأیید دو مرحله‌ای فعال است
if (!$businessSettings->isRequireTwoStepApproval()) { if (!$businessSettings->isRequireTwoStepApproval()) {
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
} }
// پیدا کردن حواله انبار
$ticket = $entityManager->getRepository(\App\Entity\StoreroomTicket::class)->findOneBy([ $ticket = $entityManager->getRepository(\App\Entity\StoreroomTicket::class)->findOneBy([
'code' => $ticketCode, 'code' => $ticketCode,
'bid' => $business 'bid' => $business
@ -54,13 +50,11 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']); return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']);
} }
// بررسی مجوز تأیید
$canApprove = $this->canUserApproveStoreroomTicket($user, $businessSettings); $canApprove = $this->canUserApproveStoreroomTicket($user, $businessSettings);
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']);
} }
// تأیید حواله
$ticket->setIsPreview(false); $ticket->setIsPreview(false);
$ticket->setIsApproved(true); $ticket->setIsApproved(true);
$ticket->setApprovedBy($user); $ticket->setApprovedBy($user);
@ -68,7 +62,6 @@ class ApprovalController extends AbstractController
$entityManager->persist($ticket); $entityManager->persist($ticket);
$entityManager->flush(); $entityManager->flush();
// ثبت لاگ
$logService->insert( $logService->insert(
'تأیید حواله انبار', 'تأیید حواله انبار',
"حواله انبار {$ticket->getCode()} توسط {$user->getFullName()} تأیید شد", "حواله انبار {$ticket->getCode()} توسط {$user->getFullName()} تأیید شد",
@ -89,7 +82,6 @@ class ApprovalController extends AbstractController
} }
} }
// تأیید فاکتور فروش
#[Route('/api/approval/approve/sales/{docId}', name: 'api_approval_approve_sales', methods: ['POST'])] #[Route('/api/approval/approve/sales/{docId}', name: 'api_approval_approve_sales', methods: ['POST'])]
public function approveSalesInvoice( public function approveSalesInvoice(
$docId, $docId,
@ -99,7 +91,6 @@ class ApprovalController extends AbstractController
EntityManagerInterface $entityManager EntityManagerInterface $entityManager
): Response { ): Response {
try { try {
// بررسی دسترسی کاربر
$acc = $access->hasRole('settings'); $acc = $access->hasRole('settings');
if (!$acc) { if (!$acc) {
throw $this->createAccessDeniedException(); throw $this->createAccessDeniedException();
@ -108,13 +99,11 @@ class ApprovalController extends AbstractController
$business = $acc['bid']; $business = $acc['bid'];
$businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId());
// بررسی اینکه آیا تأیید دو مرحله‌ای فعال است
if (!$businessSettings->isRequireTwoStepApproval()) { if (!$businessSettings->isRequireTwoStepApproval()) {
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
} }
// پیدا کردن فاکتور فروش $document = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'code' => $docId, 'code' => $docId,
'bid' => $business 'bid' => $business
]); ]);
@ -123,13 +112,11 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']); return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']);
} }
// بررسی مجوز تأیید
$canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings); $canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings);
if (!$canApprove) { if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']); return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
} }
// تأیید فاکتور
$document->setIsPreview(false); $document->setIsPreview(false);
$document->setIsApproved(true); $document->setIsApproved(true);
$document->setApprovedBy($user); $document->setApprovedBy($user);
@ -144,12 +131,22 @@ class ApprovalController extends AbstractController
$payment->setIsPreview(false); $payment->setIsPreview(false);
$payment->setIsApproved(true); $payment->setIsApproved(true);
$payment->setApprovedBy($user); $payment->setApprovedBy($user);
$entityManager->persist($payment);
}
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
'doc' => $document
]);
foreach ($rows as $row) {
$row->setIsPreview(false);
$row->setIsApproved(true);
$row->setApprovedBy($user);
$entityManager->persist($row);
} }
$entityManager->persist($document); $entityManager->persist($document);
$entityManager->flush(); $entityManager->flush();
// ثبت لاگ
$logService->insert( $logService->insert(
'تأیید فاکتور فروش', 'تأیید فاکتور فروش',
"فاکتور فروش {$document->getCode()} توسط {$user->getFullName()} تأیید شد", "فاکتور فروش {$document->getCode()} توسط {$user->getFullName()} تأیید شد",
@ -170,78 +167,8 @@ class ApprovalController extends AbstractController
} }
} }
// تأیید سند مالی #[Route('/api/approval/approve/group/sales', name: 'api_approval_approve_group_sales', methods: ['POST'])]
#[Route('/api/approval/approve/financial/{docId}', name: 'api_approval_approve_financial', methods: ['POST'])] public function approveSalesInvoiceGroup(
public function approveFinancialDocument(
$docId,
#[CurrentUser] ?User $user,
Access $access,
LogService $logService,
EntityManagerInterface $entityManager
): Response {
try {
// بررسی دسترسی کاربر
$acc = $access->hasRole('hasRole');
if (!$acc) {
throw $this->createAccessDeniedException();
}
$business = $acc['bid'];
$businessSettings = $entityManager->getRepository(Business::class)->find($business->getId());
// بررسی اینکه آیا تأیید دو مرحله‌ای فعال است
if (!$businessSettings->isRequireTwoStepApproval()) {
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
}
// پیدا کردن سند مالی
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'id' => $docId,
'bid' => $business
]);
if (!$document) {
return $this->json(['success' => false, 'message' => 'سند مالی یافت نشد']);
}
// بررسی مجوز تأیید
$canApprove = $this->canUserApproveFinancialDocument($user, $businessSettings);
if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این سند را ندارید']);
}
// تأیید سند
$document->setIsPreview(false);
$document->setIsApproved(true);
$document->setApprovedBy($user);
$entityManager->persist($document);
$entityManager->flush();
// ثبت لاگ
$logService->insert(
'تأیید سند مالی',
"سند مالی {$document->getCode()} توسط {$user->getFullName()} تأیید شد",
$user,
$business
);
return $this->json([
'success' => true,
'message' => 'سند مالی با موفقیت تأیید شد'
]);
} catch (\Exception $e) {
return $this->json([
'success' => false,
'message' => 'خطا در تأیید سند مالی: ' . $e->getMessage()
], 500);
}
}
#[Route('/api/approval/reject/{docId}', name: 'api_approval_reject', methods: ['POST'])]
public function rejectDocument(
$docId,
Request $request, Request $request,
#[CurrentUser] ?User $user, #[CurrentUser] ?User $user,
Access $access, Access $access,
@ -249,8 +176,7 @@ class ApprovalController extends AbstractController
EntityManagerInterface $entityManager EntityManagerInterface $entityManager
): Response { ): Response {
try { try {
// بررسی دسترسی کاربر $acc = $access->hasRole('settings');
$acc = $access->hasRole('owner');
if (!$acc) { if (!$acc) {
throw $this->createAccessDeniedException(); throw $this->createAccessDeniedException();
} }
@ -258,52 +184,164 @@ class ApprovalController extends AbstractController
$business = $acc['bid']; $business = $acc['bid'];
$businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId());
// بررسی اینکه آیا تأیید دو مرحله‌ای فعال است
if (!$businessSettings->isRequireTwoStepApproval()) { if (!$businessSettings->isRequireTwoStepApproval()) {
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
} }
// پیدا کردن سند $canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings);
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ if (!$canApprove) {
'id' => $docId, return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']);
'bid' => $business
]);
if (!$document) {
return $this->json(['success' => false, 'message' => 'سند یافت نشد']);
} }
// دریافت دلیل رد
$data = json_decode($request->getContent(), true); $data = json_decode($request->getContent(), true);
$rejectionReason = $data['reason'] ?? 'دلیل مشخص نشده'; $docIds = $data['docIds'] ?? [];
// رد سند foreach ($docIds as $docId) {
$document->setIsPreview(false); $document = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
$document->setIsApproved(false); 'id' => $docId,
$document->setApprovedBy(null); 'bid' => $business
]);
// ردیف‌ها نیازی به تنظیم جداگانه ندارند - از سند پیروی می‌کنند if (!$document) {
return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']);
}
$entityManager->persist($document); if ($document->isApproved()) {
$entityManager->flush(); return $this->json(['success' => false, 'message' => 'فاکتور فروش تایید شده است']);
}
$document->setIsPreview(false);
$document->setIsApproved(true);
$document->setApprovedBy($user);
$payments = [];
foreach ($document->getRelatedDocs() as $relatedDoc) {
if ($relatedDoc->getType() === 'sell_receive') {
$payments[] = $relatedDoc;
}
}
foreach ($payments as $payment) {
$payment->setIsPreview(false);
$payment->setIsApproved(true);
$payment->setApprovedBy($user);
$entityManager->persist($payment);
}
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
'doc' => $document
]);
foreach ($rows as $row) {
$row->setIsPreview(false);
$row->setIsApproved(true);
$row->setApprovedBy($user);
$entityManager->persist($row);
}
$entityManager->persist($document);
$entityManager->flush();
}
// ثبت لاگ
$logService->insert( $logService->insert(
'رد سند', 'تأیید فاکتورهای فروش',
"سند {$document->getCode()} توسط {$user->getFullName()} رد شد. دلیل: {$rejectionReason}", "فاکتورهای فروش {$docIds} توسط {$user->getFullName()} تأیید شدند",
$user, $user,
$business $business
); );
return $this->json([ return $this->json([
'success' => true, 'success' => true,
'message' => 'سند با موفقیت رد شد' 'message' => 'فاکتورهای فروش با موفقیت تأیید شدند'
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->json([ return $this->json([
'success' => false, 'success' => false,
'message' => 'خطا در رد سند: ' . $e->getMessage() 'message' => 'خطا در تأیید فاکتورهای فروش: ' . $e->getMessage()
], 500);
}
}
#[Route('/api/approval/unapprove/sales/{docId}', name: 'api_approval_unapprove_sales', methods: ['POST'])]
public function unapproveSalesInvoice(
$docId,
#[CurrentUser] ?User $user,
Access $access,
LogService $logService,
EntityManagerInterface $entityManager
): Response {
try {
$acc = $access->hasRole('settings');
if (!$acc) {
throw $this->createAccessDeniedException();
}
$business = $acc['bid'];
$businessSettings = $entityManager->getRepository(Business::class)->find($business->getId());
if (!$businessSettings->isRequireTwoStepApproval()) {
return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']);
}
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'code' => $docId,
'bid' => $business
]);
if (!$document) {
return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']);
}
$canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings);
if (!$canApprove) {
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
}
$document->setIsPreview(true);
$document->setIsApproved(false);
$document->setApprovedBy(null);
$payments = [];
foreach ($document->getRelatedDocs() as $relatedDoc) {
if ($relatedDoc->getType() === 'sell_receive') {
$payments[] = $relatedDoc;
}
}
foreach ($payments as $payment) {
$payment->setIsPreview(true);
$payment->setIsApproved(false);
$payment->setApprovedBy(null);
$entityManager->persist($payment);
}
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
'doc' => $document
]);
foreach ($rows as $row) {
$row->setIsPreview(true);
$row->setIsApproved(false);
$row->setApprovedBy(null);
$entityManager->persist($row);
}
$entityManager->persist($document);
$entityManager->flush();
$logService->insert(
'لغو تأیید فاکتور فروش',
"فاکتور فروش {$document->getCode()} توسط {$user->getFullName()} لغو تأیید شد",
$user,
$business
);
return $this->json([
'success' => true,
'message' => 'فاکتور فروش با موفقیت لغو تأیید شد'
]);
} catch (\Exception $e) {
return $this->json([
'success' => false,
'message' => 'خطا در لغو تأیید فاکتور فروش: ' . $e->getMessage()
], 500); ], 500);
} }
} }
@ -324,8 +362,7 @@ class ApprovalController extends AbstractController
$business = $acc['bid']; $business = $acc['bid'];
$businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId());
// پیدا کردن سند $document = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'id' => $docId, 'id' => $docId,
'bid' => $business 'bid' => $business
]); ]);
@ -353,17 +390,12 @@ class ApprovalController extends AbstractController
} }
} }
/**
* بررسی اینکه آیا کاربر می‌تواند سند را تأیید کند
*/
private function canUserApproveDocument(User $user, Business $business, HesabdariDoc $document): bool private function canUserApproveDocument(User $user, Business $business, HesabdariDoc $document): bool
{ {
// مدیر کسب و کار همیشه می‌تواند تأیید کند
if ($user->getEmail() === $business->getOwner()->getEmail()) { if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true; return true;
} }
// بررسی تأییدکنندگان اختصاصی بر اساس نوع سند
$documentType = $this->getDocumentType($document); $documentType = $this->getDocumentType($document);
switch ($documentType) { switch ($documentType) {
@ -378,9 +410,6 @@ class ApprovalController extends AbstractController
} }
} }
/**
* تشخیص نوع سند
*/
private function getDocumentType(HesabdariDoc $document): string private function getDocumentType(HesabdariDoc $document): string
{ {
$type = $document->getType(); $type = $document->getType();
@ -400,39 +429,30 @@ class ApprovalController extends AbstractController
return 'unknown'; return 'unknown';
} }
// بررسی مجوز تأیید حواله انبار
private function canUserApproveStoreroomTicket(User $user, Business $business): bool private function canUserApproveStoreroomTicket(User $user, Business $business): bool
{ {
// مدیر کسب و کار همیشه می‌تواند تأیید کند
if ($user->getEmail() === $business->getOwner()->getEmail()) { if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true; return true;
} }
// کاربر تأییدکننده انبار
return $business->getWarehouseApprover() === $user->getEmail(); return $business->getWarehouseApprover() === $user->getEmail();
} }
// بررسی مجوز تأیید فاکتور فروش
private function canUserApproveSalesInvoice(User $user, Business $business): bool private function canUserApproveSalesInvoice(User $user, Business $business): bool
{ {
// مدیر کسب و کار همیشه می‌تواند تأیید کند
if ($user->getEmail() === $business->getOwner()->getEmail()) { if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true; return true;
} }
// کاربر تأییدکننده فاکتور فروش
return $business->getInvoiceApprover() === $user->getEmail(); return $business->getInvoiceApprover() === $user->getEmail();
} }
// بررسی مجوز تأیید سند مالی
private function canUserApproveFinancialDocument(User $user, Business $business): bool private function canUserApproveFinancialDocument(User $user, Business $business): bool
{ {
// مدیر کسب و کار همیشه می‌تواند تأیید کند
if ($user->getEmail() === $business->getOwner()->getEmail()) { if ($user->getEmail() === $business->getOwner()->getEmail()) {
return true; return true;
} }
// کاربر تأییدکننده اسناد مالی
return $business->getFinancialApprover() === $user->getEmail(); return $business->getFinancialApprover() === $user->getEmail();
} }
} }

View file

@ -163,238 +163,235 @@ class SellController extends AbstractController
return $this->json($result); return $this->json($result);
} }
#[Route('/api/sell/mod', name: 'app_sell_mod')] // #[Route('/api/sell/mod', name: 'app_sell_mod')]
public function app_sell_mod( // public function app_sell_mod(
AccountingPermissionService $accountingPermissionService, // AccountingPermissionService $accountingPermissionService,
PluginService $pluginService, // PluginService $pluginService,
SMS $SMS, // SMS $SMS,
Provider $provider, // Provider $provider,
Extractor $extractor, // Extractor $extractor,
Request $request, // Request $request,
Access $access, // Access $access,
Log $log, // Log $log,
EntityManagerInterface $entityManager, // EntityManagerInterface $entityManager,
registryMGR $registryMGR // registryMGR $registryMGR
): JsonResponse { // ): JsonResponse {
$params = []; // $params = [];
if ($content = $request->getContent()) { // if ($content = $request->getContent()) {
$params = json_decode($content, true); // $params = json_decode($content, true);
} // }
$acc = $access->hasRole('sell'); // $acc = $access->hasRole('sell');
if (!$acc) // if (!$acc)
throw $this->createAccessDeniedException(); // throw $this->createAccessDeniedException();
$pkgcntr = $accountingPermissionService->canRegisterAccountingDoc($acc['bid']); // $pkgcntr = $accountingPermissionService->canRegisterAccountingDoc($acc['bid']);
if ($pkgcntr['code'] == 4) { // if ($pkgcntr['code'] == 4) {
return $this->json([ // return $this->json([
'result' => 4, // 'result' => 4,
'message' => $pkgcntr['message'] // 'message' => $pkgcntr['message']
]); // ]);
} // }
if (!array_key_exists('update', $params)) { // if (!array_key_exists('update', $params)) {
return $this->json($extractor->paramsNotSend()); // return $this->json($extractor->paramsNotSend());
} // }
if ($params['update'] != '') { // if ($params['update'] != '') {
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ // $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'bid' => $acc['bid'], // 'bid' => $acc['bid'],
'year' => $acc['year'], // 'year' => $acc['year'],
'code' => $params['update'], // 'code' => $params['update'],
'money' => $acc['money'] // 'money' => $acc['money']
]); // ]);
if (!$doc) // if (!$doc)
return $this->json($extractor->notFound()); // return $this->json($extractor->notFound());
// حذف سطرهای قبلی // // حذف سطرهای قبلی
$rows = $doc->getHesabdariRows(); // $rows = $doc->getHesabdariRows();
foreach ($rows as $row) // foreach ($rows as $row)
$entityManager->remove($row); // $entityManager->remove($row);
// حذف سندهای پرداخت قبلی // // حذف سندهای پرداخت قبلی
$relatedDocs = $doc->getRelatedDocs(); // $relatedDocs = $doc->getRelatedDocs();
foreach ($relatedDocs as $relatedDoc) { // foreach ($relatedDocs as $relatedDoc) {
if ($relatedDoc->getType() === 'sell_receive') { // if ($relatedDoc->getType() === 'sell_receive') {
$relatedRows = $relatedDoc->getHesabdariRows(); // $relatedRows = $relatedDoc->getHesabdariRows();
foreach ($relatedRows as $row) { // foreach ($relatedRows as $row) {
$entityManager->remove($row); // $entityManager->remove($row);
} // }
$entityManager->remove($relatedDoc); // $entityManager->remove($relatedDoc);
} // }
} // }
$entityManager->flush(); // $entityManager->flush();
} else { // } else {
$doc = new HesabdariDoc(); // $doc = new HesabdariDoc();
$doc->setBid($acc['bid']); // $doc->setBid($acc['bid']);
$doc->setYear($acc['year']); // $doc->setYear($acc['year']);
$doc->setDateSubmit(time()); // $doc->setDateSubmit(time());
$doc->setType('sell'); // $doc->setType('sell');
$doc->setSubmitter($this->getUser()); // $doc->setSubmitter($this->getUser());
$doc->setMoney($acc['money']); // $doc->setMoney($acc['money']);
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting')); // $doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
// Set approval fields based on business settings // // Set approval fields based on business settings
} // }
if ($params['transferCost'] != 0) { // if ($params['transferCost'] != 0) {
$hesabdariRow = new HesabdariRow(); // $hesabdariRow = new HesabdariRow();
$hesabdariRow->setDes('حمل و نقل کالا'); // $hesabdariRow->setDes('حمل و نقل کالا');
$hesabdariRow->setBid($acc['bid']); // $hesabdariRow->setBid($acc['bid']);
$hesabdariRow->setYear($acc['year']); // $hesabdariRow->setYear($acc['year']);
$hesabdariRow->setDoc($doc); // $hesabdariRow->setDoc($doc);
$hesabdariRow->setBs($params['transferCost']); // $hesabdariRow->setBs($params['transferCost']);
$hesabdariRow->setBd(0); // $hesabdariRow->setBd(0);
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([ // $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
'code' => '61' // 'code' => '61'
]); // ]);
$hesabdariRow->setRef($ref); // $hesabdariRow->setRef($ref);
$entityManager->persist($hesabdariRow); // $entityManager->persist($hesabdariRow);
} // }
if ($params['discountAll'] != 0) { // if ($params['discountAll'] != 0) {
$hesabdariRow = new HesabdariRow(); // $hesabdariRow = new HesabdariRow();
$hesabdariRow->setDes('تخفیف فاکتور'); // $hesabdariRow->setDes('تخفیف فاکتور');
$hesabdariRow->setBid($acc['bid']); // $hesabdariRow->setBid($acc['bid']);
$hesabdariRow->setYear($acc['year']); // $hesabdariRow->setYear($acc['year']);
$hesabdariRow->setDoc($doc); // $hesabdariRow->setDoc($doc);
$hesabdariRow->setBs(0); // $hesabdariRow->setBs(0);
$hesabdariRow->setBd($params['discountAll']); // $hesabdariRow->setBd($params['discountAll']);
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([ // $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
'code' => '104' // 'code' => '104'
]); // ]);
$hesabdariRow->setRef($ref); // $hesabdariRow->setRef($ref);
$entityManager->persist($hesabdariRow); // $entityManager->persist($hesabdariRow);
// ذخیره نوع تخفیف و درصد آن // // ذخیره نوع تخفیف و درصد آن
$doc->setDiscountType($params['discountType'] ?? 'fixed'); // $doc->setDiscountType($params['discountType'] ?? 'fixed');
if (isset($params['discountPercent'])) { // if (isset($params['discountPercent'])) {
$doc->setDiscountPercent((float) $params['discountPercent']); // $doc->setDiscountPercent((float) $params['discountPercent']);
} // }
} // }
$doc->setDes($params['des']); // $doc->setDes($params['des']);
$doc->setDate($params['date']); // $doc->setDate($params['date']);
$sumTax = 0; // $sumTax = 0;
$sumTotal = 0; // $sumTotal = 0;
foreach ($params['rows'] as $row) { // foreach ($params['rows'] as $row) {
$sumTax += $row['tax']; // $sumTax += $row['tax'];
$sumTotal += $row['sumWithoutTax']; // $sumTotal += $row['sumWithoutTax'];
$hesabdariRow = new HesabdariRow(); // $hesabdariRow = new HesabdariRow();
$hesabdariRow->setDes($row['des']); // $hesabdariRow->setDes($row['des']);
$hesabdariRow->setBid($acc['bid']); // $hesabdariRow->setBid($acc['bid']);
$hesabdariRow->setYear($acc['year']); // $hesabdariRow->setYear($acc['year']);
$hesabdariRow->setDoc($doc); // $hesabdariRow->setDoc($doc);
$hesabdariRow->setBs($row['sumWithoutTax'] + $row['tax']); // $hesabdariRow->setBs($row['sumWithoutTax'] + $row['tax']);
$hesabdariRow->setBd(0); // $hesabdariRow->setBd(0);
$hesabdariRow->setDiscount($row['discount']); // $hesabdariRow->setDiscount($row['discount']);
$hesabdariRow->setTax($row['tax']); // $hesabdariRow->setTax($row['tax']);
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([ // $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
'code' => '53' // 'code' => '53'
]); // ]);
$hesabdariRow->setRef($ref); // $hesabdariRow->setRef($ref);
$row['count'] = str_replace(',', '', $row['count']); // $row['count'] = str_replace(',', '', $row['count']);
$commodity = $entityManager->getRepository(Commodity::class)->findOneBy([ // $commodity = $entityManager->getRepository(Commodity::class)->findOneBy([
'id' => $row['commodity']['id'], // 'id' => $row['commodity']['id'],
'bid' => $acc['bid'] // 'bid' => $acc['bid']
]); // ]);
if (!$commodity) // if (!$commodity)
return $this->json($extractor->paramsNotSend()); // return $this->json($extractor->paramsNotSend());
$hesabdariRow->setCommodity($commodity); // $hesabdariRow->setCommodity($commodity);
$hesabdariRow->setCommdityCount($row['count']); // $hesabdariRow->setCommdityCount($row['count']);
$entityManager->persist($hesabdariRow); // $entityManager->persist($hesabdariRow);
if ($acc['bid']->isCommodityUpdateSellPriceAuto() == true && $commodity->getPriceSell() != $row['price']) { // if ($acc['bid']->isCommodityUpdateSellPriceAuto() == true && $commodity->getPriceSell() != $row['price']) {
$commodity->setPriceSell($row['price']); // $commodity->setPriceSell($row['price']);
$entityManager->persist($commodity); // $entityManager->persist($commodity);
} // }
} // }
$doc->setAmount($sumTax + $sumTotal - $params['discountAll'] + $params['transferCost']); // $doc->setAmount($sumTax + $sumTotal - $params['discountAll'] + $params['transferCost']);
$hesabdariRow = new HesabdariRow(); // $hesabdariRow = new HesabdariRow();
$hesabdariRow->setDes('فاکتور فروش'); // $hesabdariRow->setDes('فاکتور فروش');
$hesabdariRow->setBid($acc['bid']); // $hesabdariRow->setBid($acc['bid']);
$hesabdariRow->setYear($acc['year']); // $hesabdariRow->setYear($acc['year']);
$hesabdariRow->setDoc($doc); // $hesabdariRow->setDoc($doc);
$hesabdariRow->setBs(0); // $hesabdariRow->setBs(0);
$hesabdariRow->setBd($sumTax + $sumTotal + $params['transferCost'] - $params['discountAll']); // $hesabdariRow->setBd($sumTax + $sumTotal + $params['transferCost'] - $params['discountAll']);
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([ // $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
'code' => '3' // 'code' => '3'
]); // ]);
$hesabdariRow->setRef($ref); // $hesabdariRow->setRef($ref);
$person = $entityManager->getRepository(Person::class)->findOneBy([ // $person = $entityManager->getRepository(Person::class)->findOneBy([
'bid' => $acc['bid'], // 'bid' => $acc['bid'],
'code' => $params['person']['code'] // 'code' => $params['person']['code']
]); // ]);
if (!$person) // if (!$person)
return $this->json($extractor->paramsNotSend()); // return $this->json($extractor->paramsNotSend());
$hesabdariRow->setPerson($person); // $hesabdariRow->setPerson($person);
$entityManager->persist($hesabdariRow); // $entityManager->persist($hesabdariRow);
// Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد // if ($TwoStepApproval) {
$business = $entityManager->getRepository(Business::class)->find($acc['bid']); // $doc->setIsPreview(true);
$businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; // $doc->setIsApproved(false);
if ($businessRequire) { // $doc->setApprovedBy(null);
$doc->setIsPreview(true); // } else {
$doc->setIsApproved(false); // $doc->setIsPreview(false);
$doc->setApprovedBy(null); // $doc->setIsApproved(true);
} else { // $doc->setApprovedBy($this->getUser());
$doc->setIsPreview(false); // }
$doc->setIsApproved(true); // $entityManager->persist($doc);
$doc->setApprovedBy($this->getUser()); // $entityManager->flush();
} // if (!$doc->getShortlink()) {
$entityManager->persist($doc); // $doc->setShortlink($provider->RandomString(8));
$entityManager->flush(); // }
if (!$doc->getShortlink()) {
$doc->setShortlink($provider->RandomString(8));
}
if (array_key_exists('pair_docs', $params)) { // if (array_key_exists('pair_docs', $params)) {
foreach ($params['pair_docs'] as $pairCode) { // foreach ($params['pair_docs'] as $pairCode) {
$pair = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ // $pair = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'bid' => $acc['bid'], // 'bid' => $acc['bid'],
'code' => $pairCode, // 'code' => $pairCode,
]); // ]);
if ($pair) { // if ($pair) {
$pair->addRelatedDoc($doc); // $pair->addRelatedDoc($doc);
} // }
} // }
} // }
$entityManager->persist($doc); // $entityManager->persist($doc);
$entityManager->flush(); // $entityManager->flush();
$log->insert( // $log->insert(
'حسابداری', // 'حسابداری',
'سند حسابداری شماره ' . $doc->getCode() . ' ثبت / ویرایش شد.', // 'سند حسابداری شماره ' . $doc->getCode() . ' ثبت / ویرایش شد.',
$this->getUser(), // $this->getUser(),
$request->headers->get('activeBid'), // $request->headers->get('activeBid'),
$doc // $doc
); // );
if (array_key_exists('sms', $params)) { // if (array_key_exists('sms', $params)) {
if ($params['sms'] == true) { // if ($params['sms'] == true) {
if ($pluginService->isActive('accpro', $acc['bid']) && $person->getMobile() != '' && $acc['bid']->getTel()) { // if ($pluginService->isActive('accpro', $acc['bid']) && $person->getMobile() != '' && $acc['bid']->getTel()) {
return $this->json([ // return $this->json([
'result' => // 'result' =>
$SMS->sendByBalance( // $SMS->sendByBalance(
[$person->getnikename(), 'sell/' . $acc['bid']->getId() . '/' . $doc->getShortlink(), $acc['bid']->getName(), $acc['bid']->getTel()], // [$person->getnikename(), 'sell/' . $acc['bid']->getId() . '/' . $doc->getShortlink(), $acc['bid']->getName(), $acc['bid']->getTel()],
$registryMGR->get('sms', 'plugAccproSharefaktor'), // $registryMGR->get('sms', 'plugAccproSharefaktor'),
$person->getMobile(), // $person->getMobile(),
$acc['bid'], // $acc['bid'],
$this->getUser(), // $this->getUser(),
3 // 3
) // )
]); // ]);
} else { // } else {
return $this->json([ // return $this->json([
'result' => // 'result' =>
$SMS->sendByBalance( // $SMS->sendByBalance(
[$acc['bid']->getName(), 'sell/' . $acc['bid']->getId() . '/' . $doc->getShortlink()], // [$acc['bid']->getName(), 'sell/' . $acc['bid']->getId() . '/' . $doc->getShortlink()],
$registryMGR->get('sms', 'sharefaktor'), // $registryMGR->get('sms', 'sharefaktor'),
$person->getMobile(), // $person->getMobile(),
$acc['bid'], // $acc['bid'],
$this->getUser(), // $this->getUser(),
3 // 3
) // )
]); // ]);
} // }
} // }
} // }
return $this->json($extractor->operationSuccess()); // return $this->json($extractor->operationSuccess());
} // }
#[Route('/api/sell/label/change', name: 'app_sell_label_change')] #[Route('/api/sell/label/change', name: 'app_sell_label_change')]
public function app_sell_label_change(Request $request, Access $access, Extractor $extractor, Log $log, EntityManagerInterface $entityManager): JsonResponse public function app_sell_label_change(Request $request, Access $access, Extractor $extractor, Log $log, EntityManagerInterface $entityManager): JsonResponse
@ -1059,6 +1056,9 @@ class SellController extends AbstractController
]); ]);
} }
$business = $entityManager->getRepository(Business::class)->find($acc['bid']);
$TwoStepApproval = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
try { try {
// بررسی وجود فاکتور برای ویرایش // بررسی وجود فاکتور برای ویرایش
if (!empty($params['id'])) { if (!empty($params['id'])) {
@ -1100,6 +1100,15 @@ class SellController extends AbstractController
$doc->setSubmitter($this->getUser()); $doc->setSubmitter($this->getUser());
$doc->setMoney($acc['money']); $doc->setMoney($acc['money']);
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting')); $doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
if ($TwoStepApproval) {
$doc->setIsPreview(true);
$doc->setIsApproved(false);
$doc->setApprovedBy(null);
} else {
$doc->setIsPreview(false);
$doc->setIsApproved(true);
$doc->setApprovedBy($this->getUser());
}
} }
// تنظیم اطلاعات اصلی فاکتور // تنظیم اطلاعات اصلی فاکتور
@ -1128,6 +1137,15 @@ class SellController extends AbstractController
$hesabdariRow->setBd(0); $hesabdariRow->setBd(0);
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '61']); $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '61']);
$hesabdariRow->setRef($ref); $hesabdariRow->setRef($ref);
if ($TwoStepApproval) {
$hesabdariRow->setIsPreview(true);
$hesabdariRow->setIsApproved(false);
$hesabdariRow->setApprovedBy(null);
} else {
$hesabdariRow->setIsPreview(false);
$hesabdariRow->setIsApproved(true);
$hesabdariRow->setApprovedBy($this->getUser());
}
$entityManager->persist($hesabdariRow); $entityManager->persist($hesabdariRow);
} }
@ -1157,6 +1175,15 @@ class SellController extends AbstractController
$hesabdariRow->setBd($totalDiscount); $hesabdariRow->setBd($totalDiscount);
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '104']); $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '104']);
$hesabdariRow->setRef($ref); $hesabdariRow->setRef($ref);
if ($TwoStepApproval) {
$hesabdariRow->setIsPreview(true);
$hesabdariRow->setIsApproved(false);
$hesabdariRow->setApprovedBy(null);
} else {
$hesabdariRow->setIsPreview(false);
$hesabdariRow->setIsApproved(true);
$hesabdariRow->setApprovedBy($this->getUser());
}
$entityManager->persist($hesabdariRow); $entityManager->persist($hesabdariRow);
} }
@ -1181,6 +1208,16 @@ class SellController extends AbstractController
$hesabdariRow->setDiscountType($item['showPercentDiscount'] ? 'percent' : 'fixed'); $hesabdariRow->setDiscountType($item['showPercentDiscount'] ? 'percent' : 'fixed');
$hesabdariRow->setDiscountPercent($item['discountPercent'] ?? 0); $hesabdariRow->setDiscountPercent($item['discountPercent'] ?? 0);
if ($TwoStepApproval) {
$hesabdariRow->setIsPreview(true);
$hesabdariRow->setIsApproved(false);
$hesabdariRow->setApprovedBy(null);
} else {
$hesabdariRow->setIsPreview(false);
$hesabdariRow->setIsApproved(true);
$hesabdariRow->setApprovedBy($this->getUser());
}
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '53']); $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '53']);
$hesabdariRow->setRef($ref); $hesabdariRow->setRef($ref);
@ -1214,6 +1251,15 @@ class SellController extends AbstractController
$taxRow->setBd(0); $taxRow->setBd(0);
$taxRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '33']); $taxRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '33']);
$taxRow->setRef($taxRef); $taxRow->setRef($taxRef);
if ($TwoStepApproval) {
$taxRow->setIsPreview(true);
$taxRow->setIsApproved(false);
$taxRow->setApprovedBy(null);
} else {
$taxRow->setIsPreview(false);
$taxRow->setIsApproved(true);
$taxRow->setApprovedBy($this->getUser());
}
$entityManager->persist($taxRow); $entityManager->persist($taxRow);
} }
@ -1231,6 +1277,16 @@ class SellController extends AbstractController
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']); $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']);
$hesabdariRow->setRef($ref); $hesabdariRow->setRef($ref);
if ($TwoStepApproval) {
$hesabdariRow->setIsPreview(true);
$hesabdariRow->setIsApproved(false);
$hesabdariRow->setApprovedBy(null);
} else {
$hesabdariRow->setIsPreview(false);
$hesabdariRow->setIsApproved(true);
$hesabdariRow->setApprovedBy($this->getUser());
}
if (!isset($params['customer']) || $params['customer'] == '') { if (!isset($params['customer']) || $params['customer'] == '') {
$person = $entityManager->getRepository(Person::class)->findOneBy([ $person = $entityManager->getRepository(Person::class)->findOneBy([
'bid' => $acc['bid'], 'bid' => $acc['bid'],
@ -1256,19 +1312,6 @@ class SellController extends AbstractController
$hesabdariRow->setPerson($person); $hesabdariRow->setPerson($person);
$entityManager->persist($hesabdariRow); $entityManager->persist($hesabdariRow);
// Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد
$business = $entityManager->getRepository(Business::class)->find($acc['bid']);
$businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
if ($businessRequire) {
$doc->setIsPreview(true);
$doc->setIsApproved(false);
$doc->setApprovedBy(null);
} else {
$doc->setIsPreview(false);
$doc->setIsApproved(true);
$doc->setApprovedBy($this->getUser());
}
// ذخیره فاکتور // ذخیره فاکتور
$entityManager->persist($doc); $entityManager->persist($doc);
$entityManager->flush(); $entityManager->flush();
@ -1296,15 +1339,14 @@ class SellController extends AbstractController
$paymentDoc->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode()); $paymentDoc->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode());
$paymentDoc->setAmount($payment['amount']); $paymentDoc->setAmount($payment['amount']);
$business = $entityManager->getRepository(Business::class)->find($acc['bid']); if ($TwoStepApproval) {
$businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
if ($businessRequire) {
$paymentDoc->setIsPreview(true); $paymentDoc->setIsPreview(true);
$paymentDoc->setIsApproved(false); $paymentDoc->setIsApproved(false);
$paymentDoc->setApprovedBy(null); $paymentDoc->setApprovedBy(null);
} else { } else {
$paymentDoc->setIsPreview(false); $paymentDoc->setIsPreview(false);
$paymentDoc->setIsApproved(true); $paymentDoc->setIsApproved(true);
$paymentDoc->setApprovedBy($this->getUser());
} }
// ایجاد ارتباط با فاکتور اصلی // ایجاد ارتباط با فاکتور اصلی
@ -1323,6 +1365,15 @@ class SellController extends AbstractController
$bankRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '5']); $bankRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '5']);
$bankRow->setRef($bankRef); $bankRow->setRef($bankRef);
$bankRow->setBank($entityManager->getRepository(BankAccount::class)->find($payment['bank'])); $bankRow->setBank($entityManager->getRepository(BankAccount::class)->find($payment['bank']));
if ($TwoStepApproval) {
$bankRow->setIsPreview(true);
$bankRow->setIsApproved(false);
$bankRow->setApprovedBy(null);
} else {
$bankRow->setIsPreview(false);
$bankRow->setIsApproved(true);
$bankRow->setApprovedBy($this->getUser());
}
$entityManager->persist($bankRow); $entityManager->persist($bankRow);
} elseif ($payment['type'] === 'cashdesk') { } elseif ($payment['type'] === 'cashdesk') {
// دریافت از طریق صندوق // دریافت از طریق صندوق
@ -1336,6 +1387,15 @@ class SellController extends AbstractController
$cashdeskRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '121']); $cashdeskRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '121']);
$cashdeskRow->setRef($cashdeskRef); $cashdeskRow->setRef($cashdeskRef);
$cashdeskRow->setCashdesk($entityManager->getRepository(Cashdesk::class)->find($payment['cashdesk'])); $cashdeskRow->setCashdesk($entityManager->getRepository(Cashdesk::class)->find($payment['cashdesk']));
if ($TwoStepApproval) {
$cashdeskRow->setIsPreview(true);
$cashdeskRow->setIsApproved(false);
$cashdeskRow->setApprovedBy(null);
} else {
$cashdeskRow->setIsPreview(false);
$cashdeskRow->setIsApproved(true);
$cashdeskRow->setApprovedBy($this->getUser());
}
$entityManager->persist($cashdeskRow); $entityManager->persist($cashdeskRow);
} elseif ($payment['type'] === 'salary') { } elseif ($payment['type'] === 'salary') {
// دریافت از طریق تنخواه گردان // دریافت از طریق تنخواه گردان
@ -1349,6 +1409,15 @@ class SellController extends AbstractController
$salaryRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '122']); $salaryRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '122']);
$salaryRow->setRef($salaryRef); $salaryRow->setRef($salaryRef);
$salaryRow->setSalary($entityManager->getRepository(Salary::class)->find($payment['salary'])); $salaryRow->setSalary($entityManager->getRepository(Salary::class)->find($payment['salary']));
if ($TwoStepApproval) {
$salaryRow->setIsPreview(true);
$salaryRow->setIsApproved(false);
$salaryRow->setApprovedBy(null);
} else {
$salaryRow->setIsPreview(false);
$salaryRow->setIsApproved(true);
$salaryRow->setApprovedBy($this->getUser());
}
$entityManager->persist($salaryRow); $entityManager->persist($salaryRow);
} }
@ -1363,6 +1432,15 @@ class SellController extends AbstractController
$receiveRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']); $receiveRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']);
$receiveRow->setRef($receiveRef); $receiveRow->setRef($receiveRef);
$receiveRow->setPerson($person); $receiveRow->setPerson($person);
if ($TwoStepApproval) {
$receiveRow->setIsPreview(true);
$receiveRow->setIsApproved(false);
$receiveRow->setApprovedBy(null);
} else {
$receiveRow->setIsPreview(false);
$receiveRow->setIsApproved(true);
$receiveRow->setApprovedBy($this->getUser());
}
$entityManager->persist($receiveRow); $entityManager->persist($receiveRow);
$entityManager->persist($paymentDoc); $entityManager->persist($paymentDoc);

View file

@ -369,5 +369,47 @@ class HesabdariRow
return $this; return $this;
} }
// Approval fields
#[ORM\Column(nullable: true)]
private ?bool $isPreview = null;
#[ORM\Column(nullable: true)]
private ?bool $isApproved = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: true)]
private ?User $approvedBy = null;
public function isPreview(): ?bool
{
return $this->isPreview;
}
public function setIsPreview(?bool $isPreview): static
{
$this->isPreview = $isPreview;
return $this;
}
public function isApproved(): ?bool
{
return $this->isApproved;
}
public function setIsApproved(?bool $isApproved): static
{
$this->isApproved = $isApproved;
return $this;
}
public function getApprovedBy(): ?User
{
return $this->approvedBy;
}
public function setApprovedBy(?User $approvedBy): static
{
$this->approvedBy = $approvedBy;
return $this;
}
} }

View file

@ -1,43 +1,43 @@
<?php <?php
namespace App\Repository; // namespace App\Repository;
use App\Entity\HesabdariDoc; // use App\Entity\HesabdariDoc;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; // use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; // use Doctrine\Persistence\ManagerRegistry;
/** // /**
* @extends ServiceEntityRepository<HesabdariDoc> // * @extends ServiceEntityRepository<HesabdariDoc>
* // *
* @method HesabdariDoc|null find($id, $lockMode = null, $lockVersion = null) // * @method HesabdariDoc|null find($id, $lockMode = null, $lockVersion = null)
* @method HesabdariDoc|null findOneBy(array $criteria, array $orderBy = null) // * @method HesabdariDoc|null findOneBy(array $criteria, array $orderBy = null)
* @method HesabdariDoc[] findAll() // * @method HesabdariDoc[] findAll()
* @method HesabdariDoc[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) // * @method HesabdariDoc[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/ // */
class HesabdariDocRepository extends ServiceEntityRepository // class HesabdariDocRepository extends ServiceEntityRepository
{ // {
public function __construct(ManagerRegistry $registry) // public function __construct(ManagerRegistry $registry)
{ // {
parent::__construct($registry, HesabdariDoc::class); // parent::__construct($registry, HesabdariDoc::class);
} // }
public function save(HesabdariDoc $entity, bool $flush = false): void // public function save(HesabdariDoc $entity, bool $flush = false): void
{ // {
$this->getEntityManager()->persist($entity); // $this->getEntityManager()->persist($entity);
if ($flush) { // if ($flush) {
$this->getEntityManager()->flush(); // $this->getEntityManager()->flush();
} // }
} // }
public function remove(HesabdariDoc $entity, bool $flush = false): void // public function remove(HesabdariDoc $entity, bool $flush = false): void
{ // {
$this->getEntityManager()->remove($entity); // $this->getEntityManager()->remove($entity);
if ($flush) { // if ($flush) {
$this->getEntityManager()->flush(); // $this->getEntityManager()->flush();
} // }
} // }
// /** // /**
// * @return HesabdariDoc[] Returns an array of HesabdariDoc objects // * @return HesabdariDoc[] Returns an array of HesabdariDoc objects
@ -63,4 +63,116 @@ class HesabdariDocRepository extends ServiceEntityRepository
// ->getOneOrNullResult() // ->getOneOrNullResult()
// ; // ;
// } // }
// }
namespace App\Repository;
use App\Entity\HesabdariDoc;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class HesabdariDocRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, HesabdariDoc::class);
}
public function save(HesabdariDoc $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(HesabdariDoc $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function find(mixed $id, \Doctrine\DBAL\LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object
{
return $this->createQueryBuilder('h')
->andWhere('h.id = :id')
->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
public function findOneBy(array $criteria, array $orderBy = null): ?object
{
$qb = $this->createQueryBuilder('h');
foreach ($criteria as $field => $value) {
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
}
$qb->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))');
if ($orderBy) {
foreach ($orderBy as $field => $direction) {
$qb->addOrderBy("h.$field", $direction);
}
}
return $qb->getQuery()->getOneOrNullResult();
}
//include preview
public function findOneByIncludePreview(array $criteria, array $orderBy = null): ?object
{
$qb = $this->createQueryBuilder('h');
foreach ($criteria as $field => $value) {
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
}
if ($orderBy) {
foreach ($orderBy as $field => $direction) {
$qb->addOrderBy("h.$field", $direction);
}
}
return $qb->getQuery()->getOneOrNullResult();
}
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array
{
$qb = $this->createQueryBuilder('h');
foreach ($criteria as $field => $value) {
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
}
$qb->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))');
if ($orderBy) {
foreach ($orderBy as $field => $direction) {
$qb->addOrderBy("h.$field", $direction);
}
}
if ($limit) {
$qb->setMaxResults($limit);
}
if ($offset) {
$qb->setFirstResult($offset);
}
return $qb->getQuery()->getResult();
}
public function findAll(): array
{
return $this->createQueryBuilder('h')
->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))')
->getQuery()
->getResult();
}
} }

View file

@ -157,6 +157,12 @@
<v-icon color="success">mdi-check-decagram</v-icon> <v-icon color="success">mdi-check-decagram</v-icon>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item v-if="canShowUnapproveButton(item)" class="text-dark" title="لغو تایید فاکتور"
@click="unapproveInvoice(item.code)">
<template v-slot:prepend>
<v-icon color="red">mdi-cancel</v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" :title="$t('dialog.edit')" @click="canEditItem(item.code)"> <v-list-item class="text-dark" :title="$t('dialog.edit')" @click="canEditItem(item.code)">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon icon="mdi-file-edit"></v-icon> <v-icon icon="mdi-file-edit"></v-icon>
@ -417,7 +423,7 @@ export default defineComponent({
serverOptions: reactive({ serverOptions: reactive({
page: 1, page: 1,
rowsPerPage: 10, rowsPerPage: 10,
sortBy: [], // برای پشتیبانی از Multi-Sort sortBy: [],
}), }),
allHeaders: [ allHeaders: [
{ title: "جزئیات", value: "expand", sortable: false, visible: true, width: 80 }, { title: "جزئیات", value: "expand", sortable: false, visible: true, width: 80 },
@ -449,7 +455,6 @@ export default defineComponent({
computed: { computed: {
visibleHeaders() { visibleHeaders() {
return this.allHeaders.filter(header => { return this.allHeaders.filter(header => {
// اگر ستونهای تأیید هستند، باید دو مرحلهای فعال باشد
if ((header.value === 'approvalStatus' || header.value === 'approvedBy') && !this.business.requireTwoStepApproval) { if ((header.value === 'approvalStatus' || header.value === 'approvedBy') && !this.business.requireTwoStepApproval) {
return false; return false;
} }
@ -474,14 +479,20 @@ export default defineComponent({
Swal.fire({ text: 'هیچ موردی انتخاب نشده است.', icon: 'warning', confirmButtonText: 'قبول' }); Swal.fire({ text: 'هیچ موردی انتخاب نشده است.', icon: 'warning', confirmButtonText: 'قبول' });
return; return;
} }
const selectedInvoices = this.items.filter(inv => this.itemsSelected.includes(inv.code));
if (selectedInvoices.some(inv => !(!inv.isApproved && inv.isPreview))) {
Swal.fire({ text: 'برخی فاکتور های انتخابی تایید شده هستند.', icon: 'warning', confirmButtonText: 'قبول' });
return;
}
Swal.fire({ title: 'تایید فاکتورهای انتخابی', text: 'فاکتورهای انتخاب‌شده تایید خواهند شد.', icon: 'question', showCancelButton: true, confirmButtonText: 'بله', cancelButtonText: 'خیر' }) Swal.fire({ title: 'تایید فاکتورهای انتخابی', text: 'فاکتورهای انتخاب‌شده تایید خواهند شد.', icon: 'question', showCancelButton: true, confirmButtonText: 'بله', cancelButtonText: 'خیر' })
.then(async (r) => { .then(async (r) => {
if (!r.isConfirmed) return; if (!r.isConfirmed) return;
this.loading = true; this.loading = true;
try { try {
for (const code of this.itemsSelected) { await axios.post(`/api/approval/approve/group/sales`, {
await axios.post(`/api/sell/approve/${code}`); 'docIds': this.itemsSelected
} });
Swal.fire({ text: 'فاکتورها تایید شدند.', icon: 'success', confirmButtonText: 'قبول' }); Swal.fire({ text: 'فاکتورها تایید شدند.', icon: 'success', confirmButtonText: 'قبول' });
this.itemsSelected = []; this.itemsSelected = [];
this.loadData(); this.loadData();
@ -504,7 +515,6 @@ export default defineComponent({
this.plugins = {}; this.plugins = {};
} }
}, },
// بارگذاری اطلاعات بیزنس
async loadBusinessInfo() { async loadBusinessInfo() {
try { try {
const response = await axios.get('/api/business/get/info/' + localStorage.getItem('activeBid')); const response = await axios.get('/api/business/get/info/' + localStorage.getItem('activeBid'));
@ -514,7 +524,6 @@ export default defineComponent({
this.business = { requireTwoStepApproval: false, invoiceApprover: null }; this.business = { requireTwoStepApproval: false, invoiceApprover: null };
} }
}, },
// بارگذاری اطلاعات کاربر فعلی
async loadCurrentUser() { async loadCurrentUser() {
try { try {
const response = await axios.post('/api/business/get/user/permissions'); const response = await axios.post('/api/business/get/user/permissions');
@ -584,12 +593,12 @@ export default defineComponent({
perPage: this.serverOptions.rowsPerPage, perPage: this.serverOptions.rowsPerPage,
types: this.types.filter(t => t.checked).map(t => t.code), types: this.types.filter(t => t.checked).map(t => t.code),
dateFilter: this.dateFilter, dateFilter: this.dateFilter,
sortBy: this.serverOptions.sortBy, // ارسال اطلاعات مرتبسازی sortBy: this.serverOptions.sortBy,
}); });
const all = (response.data.items || []).map(item => ({ const all = (response.data.items || []).map(item => ({
...item, ...item,
receivedAmount: item.relatedDocsPays || 0, // نگاشت به receivedAmount receivedAmount: item.relatedDocsPays || 0,
})).filter(item => item.code && typeof item.code !== 'undefined'); })).filter(item => item.code && typeof item.code !== 'undefined');
this.items = all; this.items = all;
this.total = Number(response.data.total) || 0; this.total = Number(response.data.total) || 0;
@ -614,7 +623,6 @@ export default defineComponent({
this.loading = false; this.loading = false;
} }
}, },
// نمایش متن وضعیت تأیید
getApprovalStatusText(item) { getApprovalStatusText(item) {
if (!this.business?.requireTwoStepApproval) return 'تایید دو مرحله‌ای غیرفعال'; if (!this.business?.requireTwoStepApproval) return 'تایید دو مرحله‌ای غیرفعال';
@ -622,7 +630,6 @@ export default defineComponent({
if (item.isApproved) return 'تایید شده'; if (item.isApproved) return 'تایید شده';
return 'تایید شده'; return 'تایید شده';
}, },
// نمایش رنگ وضعیت تأیید
getApprovalStatusColor(item) { getApprovalStatusColor(item) {
if (!this.business?.requireTwoStepApproval) return 'default'; if (!this.business?.requireTwoStepApproval) return 'default';
@ -630,24 +637,18 @@ export default defineComponent({
if (item.isApproved) return 'success'; if (item.isApproved) return 'success';
return 'success'; return 'success';
}, },
// بررسی اینکه آیا دکمه تأیید باید نمایش داده شود
canShowApprovalButton(item) { canShowApprovalButton(item) {
if (!this.business?.requireTwoStepApproval) return false; if (!this.business?.requireTwoStepApproval) return false;
// اگر سند قبلاً تأیید شده، دکمه تأیید نمایش داده نشود if (item?.isApproved || (!item?.isPreview && !item?.isApproved)) return false;
if (item?.isApproved) return false;
// مدیر کسب و کار همیشه میتواند تأیید کند
// یا کاربر تأییدکننده فاکتور فروش
return this.business?.invoiceApprover === this.currentUser?.email || this.currentUser?.owner === true; return this.business?.invoiceApprover === this.currentUser?.email || this.currentUser?.owner === true;
}, },
// تایید فاکتور فروش
async approveInvoice(code) { async approveInvoice(code) {
try { try {
this.loading = true; this.loading = true;
await axios.post(`/api/approval/approve/sales/${code}`); await axios.post(`/api/approval/approve/sales/${code}`);
// بهروزرسانی دادهها
await this.loadData(); await this.loadData();
Swal.fire({ text: 'فاکتور تایید شد', icon: 'success', confirmButtonText: 'قبول' }); Swal.fire({ text: 'فاکتور تایید شد', icon: 'success', confirmButtonText: 'قبول' });
@ -657,6 +658,23 @@ export default defineComponent({
this.loading = false; this.loading = false;
} }
}, },
canShowUnapproveButton(item) {
return !this.canShowApprovalButton(item);
},
async unapproveInvoice(code) {
try {
this.loading = true;
await axios.post(`/api/approval/unapprove/sales/${code}`);
await this.loadData();
Swal.fire({ text: 'تایید فاکتور لغو شد', icon: 'success', confirmButtonText: 'قبول' });
} catch (error) {
Swal.fire({ text: 'خطا در لغو تایید فاکتور: ' + (error.response?.data?.message || error.message), icon: 'error', confirmButtonText: 'قبول' });
} finally {
this.loading = false;
}
},
canEditItem(code) { canEditItem(code) {
this.loading = true; this.loading = true;
axios.post('/api/sell/edit/can/' + code).then((response) => { axios.post('/api/sell/edit/can/' + code).then((response) => {
@ -734,14 +752,12 @@ export default defineComponent({
}).then(async (response) => { }).then(async (response) => {
try { try {
if (response.data && response.data.id) { if (response.data && response.data.id) {
// دریافت فایل PDF
const pdfResponse = await axios({ const pdfResponse = await axios({
method: 'get', method: 'get',
url: '/front/print/' + response.data.id, url: '/front/print/' + response.data.id,
responseType: 'arraybuffer' responseType: 'arraybuffer'
}); });
// ایجاد لینک دانلود
var fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data])); var fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
var fileLink = document.createElement('a'); var fileLink = document.createElement('a');
fileLink.href = fileURL; fileLink.href = fileURL;
@ -824,7 +840,7 @@ export default defineComponent({
updateServerOptions(options) { updateServerOptions(options) {
this.serverOptions.page = options.page; this.serverOptions.page = options.page;
this.serverOptions.rowsPerPage = options.itemsPerPage; this.serverOptions.rowsPerPage = options.itemsPerPage;
this.serverOptions.sortBy = options.sortBy || []; // مدیریت Multi-Sort this.serverOptions.sortBy = options.sortBy || [];
this.loadData(); this.loadData();
}, },
debouncedLoadData: debounce(function () { debouncedLoadData: debounce(function () {