almost finish buy system with new changes in hesabdariDoc
This commit is contained in:
parent
45c03051a0
commit
9af86b989b
35
hesabixCore/migrations/Version20250819234842.php
Normal file
35
hesabixCore/migrations/Version20250819234842.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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 Version20250819234842 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_doc CHANGE is_preview is_preview TINYINT(1) DEFAULT 0, CHANGE is_approved is_approved TINYINT(1) DEFAULT 1
|
||||
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_doc CHANGE is_preview is_preview TINYINT(1) DEFAULT NULL, CHANGE is_approved is_approved TINYINT(1) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
|
@ -250,7 +250,7 @@ class ApprovalController extends AbstractController
|
|||
|
||||
foreach ($docIds as $docId) {
|
||||
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
|
||||
'id' => $docId,
|
||||
'code' => $docId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
|
@ -378,6 +378,274 @@ class ApprovalController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
#[Route('/api/approval/approve/buy/{docId}', name: 'api_approval_approve_buy', methods: ['POST'])]
|
||||
public function approveBuyInvoice(
|
||||
$docId,
|
||||
#[CurrentUser] ?User $user,
|
||||
Access $access,
|
||||
LogService $logService,
|
||||
EntityManagerInterface $entityManager
|
||||
): Response {
|
||||
try {
|
||||
$acc = $access->hasRole('buy');
|
||||
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)->findOneByIncludePreview([
|
||||
'code' => $docId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
if (!$document) {
|
||||
return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']);
|
||||
}
|
||||
|
||||
$canApprove = $this->canUserApproveBuyInvoice($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/approve/group/buy', name: 'api_approval_approve_group_buy', methods: ['POST'])]
|
||||
public function approveBuyInvoiceGroup(
|
||||
Request $request,
|
||||
#[CurrentUser] ?User $user,
|
||||
Access $access,
|
||||
LogService $logService,
|
||||
EntityManagerInterface $entityManager
|
||||
): Response {
|
||||
try {
|
||||
$acc = $access->hasRole('buy');
|
||||
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' => 'تأیید دو مرحلهای فعال نیست']);
|
||||
}
|
||||
|
||||
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings);
|
||||
if (!$canApprove) {
|
||||
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$docIds = $data['docIds'] ?? [];
|
||||
|
||||
foreach ($docIds as $docId) {
|
||||
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
|
||||
'code' => $docId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
if (!$document) {
|
||||
return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']);
|
||||
}
|
||||
|
||||
if ($document->isApproved()) {
|
||||
return $this->json(['success' => false, 'message' => 'فاکتور خرید تایید شده است']);
|
||||
}
|
||||
|
||||
$document->setIsPreview(false);
|
||||
$document->setIsApproved(true);
|
||||
$document->setApprovedBy($user);
|
||||
|
||||
$entityManager->persist($document);
|
||||
}
|
||||
|
||||
$entityManager->flush();
|
||||
|
||||
$logService->insert(
|
||||
'تأیید فاکتورهای خرید',
|
||||
"فاکتورهای خرید " . implode(', ', $docIds) . " توسط {$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/unapprove/buy/{docId}', name: 'api_approval_unapprove_buy', methods: ['POST'])]
|
||||
public function unapproveBuyInvoice(
|
||||
$docId,
|
||||
#[CurrentUser] ?User $user,
|
||||
Access $access,
|
||||
LogService $logService,
|
||||
EntityManagerInterface $entityManager
|
||||
): Response {
|
||||
try {
|
||||
$acc = $access->hasRole('buy');
|
||||
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)->findOneByIncludePreview([
|
||||
'code' => $docId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
if (!$document) {
|
||||
return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']);
|
||||
}
|
||||
|
||||
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings);
|
||||
if (!$canApprove) {
|
||||
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
|
||||
}
|
||||
|
||||
$document->setIsPreview(true);
|
||||
$document->setIsApproved(false);
|
||||
$document->setApprovedBy(null);
|
||||
|
||||
$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/unapprove/group/buy', name: 'api_approval_unapprove_group_buy', methods: ['POST'])]
|
||||
public function unapproveBuyInvoiceGroup(
|
||||
Request $request,
|
||||
#[CurrentUser] ?User $user,
|
||||
Access $access,
|
||||
LogService $logService,
|
||||
EntityManagerInterface $entityManager
|
||||
): Response {
|
||||
try {
|
||||
$acc = $access->hasRole('buy');
|
||||
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' => 'تأیید دو مرحلهای فعال نیست']);
|
||||
}
|
||||
|
||||
$canApprove = $this->canUserApproveBuyInvoice($user, $businessSettings);
|
||||
if (!$canApprove) {
|
||||
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتورها را ندارید']);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$docIds = $data['docIds'] ?? [];
|
||||
|
||||
foreach ($docIds as $docId) {
|
||||
$document = $entityManager->getRepository(HesabdariDoc::class)->findOneByIncludePreview([
|
||||
'code' => $docId,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
if (!$document) {
|
||||
return $this->json(['success' => false, 'message' => 'فاکتور خرید یافت نشد']);
|
||||
}
|
||||
|
||||
if (!$document->isApproved()) {
|
||||
return $this->json(['success' => false, 'message' => 'فاکتور خرید تایید نشده است']);
|
||||
}
|
||||
|
||||
$document->setIsPreview(true);
|
||||
$document->setIsApproved(false);
|
||||
$document->setApprovedBy(null);
|
||||
|
||||
$entityManager->persist($document);
|
||||
}
|
||||
|
||||
$entityManager->flush();
|
||||
|
||||
$logService->insert(
|
||||
'لغو تأیید فاکتورهای خرید',
|
||||
"فاکتورهای خرید " . implode(', ', $docIds) . " توسط {$user->getFullName()} لغو تأیید شدند",
|
||||
$user,
|
||||
$business
|
||||
);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'message' => 'فاکتورهای خرید با موفقیت لغو تأیید شدند'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در لغو تأیید فاکتورهای خرید: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function canUserApproveDocument(User $user, Business $business, HesabdariDoc $document): bool
|
||||
{
|
||||
if ($user->getEmail() === $business->getOwner()->getEmail()) {
|
||||
|
@ -432,4 +700,13 @@ class ApprovalController extends AbstractController
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,6 +252,9 @@ class BuyController extends AbstractController
|
|||
return $this->json($extractor->notFound());
|
||||
}
|
||||
foreach ($params['items'] as $item) {
|
||||
if (!$item || !isset($item['code'])) {
|
||||
continue;
|
||||
}
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
|
@ -286,77 +289,177 @@ class BuyController extends AbstractController
|
|||
return $this->json($extractor->operationSuccess());
|
||||
}
|
||||
|
||||
#[Route('/api/buy/docs/search', name: 'app_buy_docs_search')]
|
||||
#[Route('/api/buy/docs/search', name: 'app_buy_docs_search', methods: ['POST'])]
|
||||
public function app_buy_docs_search(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('buy');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$searchTerm = $params['search'] ?? '';
|
||||
$page = max(1, $params['page'] ?? 1);
|
||||
$perPage = max(1, min(100, $params['perPage'] ?? 10));
|
||||
$types = $params['types'] ?? [];
|
||||
$sortBy = $params['sortBy'] ?? [];
|
||||
|
||||
$queryBuilder = $entityManager->createQueryBuilder()
|
||||
->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount')
|
||||
->addSelect('d.isPreview, d.isApproved')
|
||||
->addSelect('u.fullName as submitter')
|
||||
->addSelect('approver.fullName as approvedByName, approver.id as approvedById, approver.email as approvedByEmail')
|
||||
->addSelect('l.code as labelCode, l.label as labelLabel')
|
||||
->from(HesabdariDoc::class, 'd')
|
||||
->leftJoin('d.submitter', 'u')
|
||||
->leftJoin('d.approvedBy', 'approver')
|
||||
->leftJoin('d.InvoiceLabel', 'l')
|
||||
->leftJoin('d.hesabdariRows', 'r')
|
||||
->where('d.bid = :bid')
|
||||
->andWhere('d.year = :year')
|
||||
->andWhere('d.type = :type')
|
||||
->andWhere('d.money = :money')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('year', $acc['year'])
|
||||
->setParameter('type', 'buy')
|
||||
->setParameter('money', $acc['money']);
|
||||
|
||||
if ($searchTerm) {
|
||||
$queryBuilder->leftJoin('r.person', 'p')
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->orX(
|
||||
'd.code LIKE :search',
|
||||
'd.des LIKE :search',
|
||||
'd.date LIKE :search',
|
||||
'd.amount LIKE :search',
|
||||
'p.nikename LIKE :search',
|
||||
'p.mobile LIKE :search'
|
||||
)
|
||||
)
|
||||
->setParameter('search', "%$searchTerm%");
|
||||
}
|
||||
$data = $entityManager->getRepository(HesabdariDoc::class)->findBy([
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'type' => 'buy',
|
||||
'money' => $acc['money']
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
|
||||
if (!empty($types)) {
|
||||
$queryBuilder->andWhere('l.code IN (:types)')
|
||||
->setParameter('types', $types);
|
||||
}
|
||||
|
||||
// فیلدهای معتبر برای مرتبسازی توی دیتابیس
|
||||
$validDbFields = [
|
||||
'id' => 'd.id',
|
||||
'dateSubmit' => 'd.dateSubmit',
|
||||
'date' => 'd.date',
|
||||
'type' => 'd.type',
|
||||
'code' => 'd.code',
|
||||
'des' => 'd.des',
|
||||
'amount' => 'd.amount',
|
||||
'mdate' => 'd.mdate',
|
||||
'plugin' => 'd.plugin',
|
||||
'refData' => 'd.refData',
|
||||
'shortlink' => 'd.shortlink',
|
||||
'isPreview' => 'd.isPreview',
|
||||
'isApproved' => 'd.isApproved',
|
||||
'approvedBy' => 'd.approvedBy',
|
||||
'submitter' => 'u.fullName',
|
||||
'label' => 'l.label',
|
||||
];
|
||||
|
||||
// اعمال مرتبسازی توی دیتابیس
|
||||
if (!empty($sortBy)) {
|
||||
foreach ($sortBy as $sort) {
|
||||
$key = $sort['key'] ?? 'id';
|
||||
$direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? 'DESC' : 'ASC';
|
||||
if (isset($validDbFields[$key])) {
|
||||
$queryBuilder->addOrderBy($validDbFields[$key], $direction);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$queryBuilder->orderBy('d.id', 'DESC');
|
||||
}
|
||||
|
||||
$totalItemsQuery = clone $queryBuilder;
|
||||
$totalItems = $totalItemsQuery->select('COUNT(DISTINCT d.id)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
$queryBuilder->setFirstResult(($page - 1) * $perPage)
|
||||
->setMaxResults($perPage);
|
||||
|
||||
$docs = $queryBuilder->getQuery()->getArrayResult();
|
||||
|
||||
$dataTemp = [];
|
||||
foreach ($data as $item) {
|
||||
$temp = [
|
||||
'id' => $item->getId(),
|
||||
'dateSubmit' => $item->getDateSubmit(),
|
||||
'date' => $item->getDate(),
|
||||
'type' => $item->getType(),
|
||||
'code' => $item->getCode(),
|
||||
'des' => $item->getDes(),
|
||||
'amount' => $item->getAmount(),
|
||||
'submitter' => $item->getSubmitter()->getFullName(),
|
||||
foreach ($docs as $doc) {
|
||||
$item = [
|
||||
'id' => $doc['id'],
|
||||
'dateSubmit' => $doc['dateSubmit'],
|
||||
'date' => $doc['date'],
|
||||
'type' => $doc['type'],
|
||||
'code' => $doc['code'],
|
||||
'des' => $doc['des'],
|
||||
'amount' => $doc['amount'],
|
||||
'submitter' => $doc['submitter'],
|
||||
'label' => $doc['labelCode'] ? [
|
||||
'code' => $doc['labelCode'],
|
||||
'label' => $doc['labelLabel']
|
||||
] : null,
|
||||
'isPreview' => $doc['isPreview'],
|
||||
'isApproved' => $doc['isApproved'],
|
||||
'approvedBy' => $doc['approvedByName'] ? [
|
||||
'fullName' => $doc['approvedByName'],
|
||||
'id' => $doc['approvedById'],
|
||||
'email' => $doc['approvedByEmail']
|
||||
] : null,
|
||||
];
|
||||
$mainRow = $entityManager->getRepository(HesabdariRow::class)->getNotEqual($item, 'person');
|
||||
$temp['person'] = '';
|
||||
if ($mainRow)
|
||||
$temp['person'] = Explore::ExplorePerson($mainRow->getPerson());
|
||||
|
||||
$temp['label'] = null;
|
||||
if ($item->getInvoiceLabel()) {
|
||||
$temp['label'] = [
|
||||
'code' => $item->getInvoiceLabel()->getCode(),
|
||||
'label' => $item->getInvoiceLabel()->getLabel()
|
||||
];
|
||||
}
|
||||
$mainRow = $entityManager->getRepository(HesabdariRow::class)
|
||||
->createQueryBuilder('r')
|
||||
->where('r.doc = :docId')
|
||||
->andWhere('r.person IS NOT NULL')
|
||||
->setParameter('docId', $doc['id'])
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
$item['person'] = $mainRow && $mainRow->getPerson() ? Explore::ExplorePerson($mainRow->getPerson()) : null;
|
||||
|
||||
$temp['relatedDocsCount'] = count($item->getRelatedDocs());
|
||||
$pays = 0;
|
||||
foreach ($item->getRelatedDocs() as $relatedDoc) {
|
||||
$pays += $relatedDoc->getAmount();
|
||||
}
|
||||
$temp['relatedDocsPays'] = $pays;
|
||||
// محاسبه پرداختیها
|
||||
$relatedDocs = $entityManager->getRepository(HesabdariDoc::class)
|
||||
->createQueryBuilder('rd')
|
||||
->select('SUM(rd.amount) as total_pays, COUNT(rd.id) as count_docs')
|
||||
->innerJoin('rd.relatedDocs', 'rel')
|
||||
->where('rel.id = :sourceDocId')
|
||||
->andWhere('rd.bid = :bidId')
|
||||
->setParameter('sourceDocId', $doc['id'])
|
||||
->setParameter('bidId', $acc['bid']->getId())
|
||||
->getQuery()
|
||||
->getSingleResult();
|
||||
|
||||
$temp['commodities'] = [];
|
||||
foreach ($item->getHesabdariRows() as $item) {
|
||||
if ($item->getRef()->getCode() == '51') {
|
||||
$temp['discountAll'] = $item->getBs();
|
||||
} elseif ($item->getRef()->getCode() == '90') {
|
||||
$temp['transferCost'] = $item->getBd();
|
||||
}
|
||||
if ($item->getCommodity()) {
|
||||
$temp['commodities'][] = Explore::ExploreCommodity($item->getCommodity(), $item->getCommdityCount());
|
||||
$item['relatedDocsCount'] = (int) $relatedDocs['count_docs'];
|
||||
$item['relatedDocsPays'] = $relatedDocs['total_pays'] ?? 0;
|
||||
|
||||
// محاسبه کالاها و تخفیف/هزینه حمل
|
||||
$item['commodities'] = [];
|
||||
$item['discountAll'] = 0;
|
||||
$item['transferCost'] = 0;
|
||||
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $doc['id']]);
|
||||
foreach ($rows as $row) {
|
||||
if ($row->getRef()->getCode() == '51') {
|
||||
$item['discountAll'] = $row->getBs();
|
||||
} elseif ($row->getRef()->getCode() == '90') {
|
||||
$item['transferCost'] = $row->getBd();
|
||||
} elseif ($row->getCommodity()) {
|
||||
$item['commodities'][] = Explore::ExploreCommodity($row->getCommodity(), $row->getCommdityCount());
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('discountAll', $temp))
|
||||
$temp['discountAll'] = 0;
|
||||
if (!array_key_exists('transferCost', $temp))
|
||||
$temp['transferCost'] = 0;
|
||||
|
||||
$dataTemp[] = $temp;
|
||||
$dataTemp[] = $item;
|
||||
}
|
||||
return $this->json($dataTemp);
|
||||
|
||||
return $this->json([
|
||||
'items' => $dataTemp,
|
||||
'total' => (int) $totalItems,
|
||||
'page' => $page,
|
||||
'perPage' => $perPage,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/buy/posprinter/invoice', name: 'app_buy_posprinter_invoice')]
|
||||
|
@ -425,6 +528,8 @@ class BuyController extends AbstractController
|
|||
return $this->json(['id' => $pdfPid]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[Route('/api/buy/print/invoice', name: 'app_buy_print_invoice')]
|
||||
public function app_buy_print_invoice(Printers $printers, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, TemplateRenderer $renderer): JsonResponse
|
||||
{
|
||||
|
@ -586,4 +691,43 @@ class BuyController extends AbstractController
|
|||
}
|
||||
return $this->json(['id' => $pdfPid]);
|
||||
}
|
||||
|
||||
#[Route('/api/buy/approve/{code}', name: 'app_buy_approve', methods: ['POST'])]
|
||||
public function approveBuyDoc(string $code, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('buy');
|
||||
if (!$acc) throw $this->createAccessDeniedException();
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code,
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
if (!$doc) throw $this->createNotFoundException('فاکتور یافت نشد');
|
||||
$doc->setIsPreview(false);
|
||||
$doc->setIsApproved(true);
|
||||
$doc->setApprovedBy($this->getUser());
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
return $this->json(['result' => 0]);
|
||||
}
|
||||
|
||||
#[Route('/api/buy/payment/approve/{code}', name: 'app_buy_payment_approve', methods: ['POST'])]
|
||||
public function approveBuyPayment(string $code, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('buy');
|
||||
if (!$acc) throw $this->createAccessDeniedException();
|
||||
$paymentDoc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code,
|
||||
'money' => $acc['money'],
|
||||
'type' => 'buy_pay'
|
||||
]);
|
||||
if (!$paymentDoc) throw $this->createNotFoundException('سند پرداخت یافت نشد');
|
||||
$paymentDoc->setIsPreview(false);
|
||||
$paymentDoc->setIsApproved(true);
|
||||
$paymentDoc->setApprovedBy($this->getUser());
|
||||
$entityManager->persist($paymentDoc);
|
||||
$entityManager->flush();
|
||||
return $this->json(['result' => 0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -728,11 +728,11 @@ class HesabdariDoc
|
|||
}
|
||||
|
||||
// Approval fields
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $isPreview = null;
|
||||
#[ORM\Column(nullable: true, options: ['default' => false])]
|
||||
private ?bool $isPreview = false;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $isApproved = null;
|
||||
#[ORM\Column(nullable: true, options: ['default' => true])]
|
||||
private ?bool $isApproved = true;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
|
|
|
@ -109,7 +109,11 @@ class HesabdariDocRepository extends ServiceEntityRepository
|
|||
$qb = $this->createQueryBuilder('h');
|
||||
|
||||
foreach ($criteria as $field => $value) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
if ($field === 'bid' && is_object($value)) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value->getId());
|
||||
} else {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$qb->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))');
|
||||
|
@ -129,7 +133,11 @@ class HesabdariDocRepository extends ServiceEntityRepository
|
|||
$qb = $this->createQueryBuilder('h');
|
||||
|
||||
foreach ($criteria as $field => $value) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
if ($field === 'bid' && is_object($value)) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value->getId());
|
||||
} else {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
|
@ -146,7 +154,11 @@ class HesabdariDocRepository extends ServiceEntityRepository
|
|||
$qb = $this->createQueryBuilder('h');
|
||||
|
||||
foreach ($criteria as $field => $value) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
if ($field === 'bid' && is_object($value)) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value->getId());
|
||||
} else {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$qb->andWhere('(h.isApproved = 1 OR (h.isApproved = 0 AND h.isPreview = 0))');
|
||||
|
@ -175,4 +187,34 @@ class HesabdariDocRepository extends ServiceEntityRepository
|
|||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
//include preview
|
||||
public function findByIncludePreview(array $criteria, array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('h');
|
||||
|
||||
foreach ($criteria as $field => $value) {
|
||||
if ($field === 'bid' && is_object($value)) {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value->getId());
|
||||
} else {
|
||||
$qb->andWhere("h.$field = :$field")->setParameter($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -28,6 +28,17 @@
|
|||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-subheader color="primary" v-if="checkApprover()">عملیات گروهی تایید</v-list-subheader>
|
||||
<v-list-item v-if="currentTab === 'pending' && checkApprover()" class="text-dark" title="تایید فاکتورهای انتخابی" @click="approveSelectedInvoices">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="success" icon="mdi-check-decagram"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="currentTab === 'approved' && checkApprover()" class="text-dark" title="لغو تایید فاکتورهای انتخابی" @click="unapproveSelectedInvoices">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="red" icon="mdi-cancel"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader color="primary">تغییر برچسبها</v-list-subheader>
|
||||
<v-list-item v-for="item in types" class="text-dark" :title="'تغییر به ' + item.label" @click="changeLabel(item)">
|
||||
<template v-slot:prepend>
|
||||
|
@ -43,6 +54,14 @@
|
|||
</v-menu>
|
||||
</v-toolbar>
|
||||
|
||||
<!-- Tabs for two-step approval -->
|
||||
<div v-if="business.requireTwoStepApproval" class="px-2 pt-2">
|
||||
<v-tabs v-model="currentTab" color="primary" density="comfortable" grow>
|
||||
<v-tab value="approved">فاکتورهای تایید شده</v-tab>
|
||||
<v-tab value="pending">فاکتورهای در انتظار تایید</v-tab>
|
||||
</v-tabs>
|
||||
</div>
|
||||
|
||||
<!-- فیلد جستجو و فیلتر -->
|
||||
<v-text-field
|
||||
hide-details
|
||||
|
@ -88,48 +107,50 @@
|
|||
</v-text-field>
|
||||
|
||||
<!-- جدول اصلی -->
|
||||
<EasyDataTable
|
||||
v-model:items-selected="itemsSelected"
|
||||
table-class-name="customize-table"
|
||||
show-index
|
||||
alternating
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
theme-color="#1d90ff"
|
||||
header-text-direction="center"
|
||||
body-text-direction="center"
|
||||
rowsPerPageMessage="تعداد سطر"
|
||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
|
||||
rowsOfPageSeparatorMessage="از"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #item-operation="{ code }">
|
||||
<v-data-table-server v-model:items-per-page="serverOptions.rowsPerPage" v-model:page="serverOptions.page"
|
||||
:headers="visibleHeaders" :items="displayItems" :items-length="displayTotal" :loading="loading"
|
||||
:no-data-text="$t('table.no_data')" v-model="itemsSelected" show-select class="elevation-1 data-table-wrapper" item-value="code"
|
||||
:max-height="tableHeight" :header-props="{ class: 'custom-header' }" @update:options="updateServerOptions"
|
||||
multi-sort>
|
||||
<template v-slot:item.operation="{ item }">
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="text" size="small" color="error" icon="mdi-menu" v-bind="props" />
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item :to="'/acc/accounting/view/' + code" class="text-dark" title="سند حسابداری">
|
||||
<v-list-item :to="'/acc/accounting/view/' + item.code" class="text-dark" title="سند حسابداری">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="green-darken-4" icon="mdi-file"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item :to="'/acc/buy/view/' + code" class="text-dark" title="مشاهده">
|
||||
<v-list-item :to="'/acc/buy/view/' + item.code" class="text-dark" title="مشاهده">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="green-darken-4" icon="mdi-eye"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item @click="openPrintModal(code)" class="text-dark" title="خروجی PDF">
|
||||
<v-list-item @click="openPrintModal(item.code)" class="text-dark" title="خروجی PDF">
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-file-pdf-box"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item @click="canEditItem(code)" class="text-dark" title="ویرایش">
|
||||
<v-list-item v-if="canShowApprovalButton(item)" class="text-dark" title="تایید فاکتور"
|
||||
@click="approveInvoice(item.code)">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="success">mdi-check-decagram</v-icon>
|
||||
</template>
|
||||
</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 @click="canEditItem(item.code)" class="text-dark" title="ویرایش">
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-file-edit"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item @click="deleteItem(code)" class="text-dark" title="حذف">
|
||||
<v-list-item @click="deleteItem(item.code)" class="text-dark" title="حذف">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="deep-orange-accent-4" icon="mdi-trash-can"></v-icon>
|
||||
</template>
|
||||
|
@ -137,48 +158,56 @@
|
|||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
<template #item-label="{ label }">
|
||||
<span v-if="label">
|
||||
<span v-if="label.code == 'payed'" class="text-success">{{ label.label }}</span>
|
||||
<span v-if="label.code == 'returned'" class="text-danger">{{ label.label }}</span>
|
||||
<span v-if="label.code == 'accepted'" class="text-info">{{ label.label }}</span>
|
||||
<template v-slot:item.label="{ item }">
|
||||
<span v-if="item.label">
|
||||
<span v-if="item.label.code == 'payed'" class="text-success">{{ item.label.label }}</span>
|
||||
<span v-if="item.label.code == 'returned'" class="text-danger">{{ item.label.label }}</span>
|
||||
<span v-if="item.label.code == 'accepted'" class="text-info">{{ item.label.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #item-des="{ des }">
|
||||
{{ des.replace("فاکتور خرید:", "") }}
|
||||
<template v-slot:item.des="{ item }">
|
||||
{{ item.des.replace("فاکتور خرید:", "") }}
|
||||
</template>
|
||||
<template #item-relatedDocsCount="{ relatedDocsCount, relatedDocsPays }">
|
||||
<span v-if="relatedDocsCount != '0'" class="text-success">
|
||||
<v-icon small>mdi-currency-usd</v-icon>
|
||||
{{ $filters.formatNumber(relatedDocsPays) }}
|
||||
<template v-slot:item.relatedDocsCount="{ item }">
|
||||
<span v-if="item.relatedDocsCount != '0'" class="text-success">
|
||||
{{ $filters.formatNumber(item.relatedDocsPays) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #item-amount="{ amount }">
|
||||
<template v-slot:item.amount="{ item }">
|
||||
<span class="text-dark">
|
||||
{{ $filters.formatNumber(amount) }}
|
||||
{{ $filters.formatNumber(item.amount) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #item-transferCost="{ transferCost }">
|
||||
<template v-slot:item.transferCost="{ item }">
|
||||
<span class="text-dark">
|
||||
{{ $filters.formatNumber(transferCost) }}
|
||||
{{ $filters.formatNumber(item.transferCost) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #item-discountAll="{ discountAll }">
|
||||
<template v-slot:item.discountAll="{ item }">
|
||||
<span class="text-dark">
|
||||
{{ $filters.formatNumber(discountAll) }}
|
||||
{{ $filters.formatNumber(item.discountAll) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #item-person="{ person }">
|
||||
<router-link :to="'/acc/persons/card/view/' + person.code">
|
||||
{{ person.nikename }}
|
||||
<template v-slot:item.person="{ item }">
|
||||
<router-link v-if="item.person" :to="'/acc/persons/card/view/' + item.person.code">
|
||||
{{ item.person.nikename }}
|
||||
</router-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-slot:item.approvalStatus="{ item }">
|
||||
<v-chip size="small" :color="getApprovalStatusColor(item)">
|
||||
{{ getApprovalStatusText(item) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template v-slot:item.approvedBy="{ item }">
|
||||
{{ item.approvedBy?.fullName || '-' }}
|
||||
</template>
|
||||
<template v-slot:item.code="{ item }">
|
||||
<router-link :to="'/acc/buy/view/' + item.code">
|
||||
{{ item.code }}
|
||||
</router-link>
|
||||
</template>
|
||||
<template #item-code="{ code }">
|
||||
<router-link :to="'/acc/buy/view/' + code">
|
||||
{{ code }}
|
||||
</router-link>
|
||||
</template>
|
||||
</EasyDataTable>
|
||||
</v-data-table-server>
|
||||
|
||||
<!-- مودال چاپ -->
|
||||
<v-dialog v-model="printModal" width="auto">
|
||||
|
@ -236,48 +265,87 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
import Swal from "sweetalert2";
|
||||
import { ref } from "vue";
|
||||
import { defineComponent, reactive, watch } from "vue";
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "list",
|
||||
data: () => ({
|
||||
printModal: false,
|
||||
printOptions: {
|
||||
pays: true,
|
||||
note: true,
|
||||
bidInfo: true,
|
||||
taxInfo: true,
|
||||
discountInfo: true,
|
||||
selectedPrintCode: 0,
|
||||
paper: 'A4-L'
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'approved',
|
||||
printModal: false,
|
||||
printOptions: {
|
||||
pays: true,
|
||||
note: true,
|
||||
bidInfo: true,
|
||||
taxInfo: true,
|
||||
discountInfo: true,
|
||||
selectedPrintCode: 0,
|
||||
paper: 'A4-L'
|
||||
},
|
||||
paperSizes: [
|
||||
{ title: 'A4 عمودی', value: 'A4' },
|
||||
{ title: 'A4 افقی', value: 'A4-L' },
|
||||
{ title: 'A5 عمودی', value: 'A5' },
|
||||
{ title: 'A5 افقی', value: 'A5-L' },
|
||||
],
|
||||
business: { requireTwoStepApproval: false, approvers: { buyInvoice: null } },
|
||||
currentUser: { email: '', owner: false },
|
||||
sumSelected: 0,
|
||||
sumTotal: 0,
|
||||
itemsSelected: [],
|
||||
searchValue: '',
|
||||
types: [],
|
||||
loading: false,
|
||||
items: [],
|
||||
itemsApproved: [],
|
||||
itemsPending: [],
|
||||
total: 0,
|
||||
totalApproved: 0,
|
||||
totalPending: 0,
|
||||
serverOptions: reactive({
|
||||
page: 1,
|
||||
rowsPerPage: 10,
|
||||
sortBy: [],
|
||||
}),
|
||||
allHeaders: [
|
||||
{ title: "عملیات", value: "operation", sortable: false, visible: true, width: 100 },
|
||||
{ title: "فاکتور", value: "code", sortable: true, visible: true, width: 120 },
|
||||
{ title: "تاریخ", value: "date", sortable: true, visible: true, width: 120 },
|
||||
{ title: "خریدار", value: "person", sortable: true, visible: true, width: 150 },
|
||||
{ title: "وضعیت تایید", value: "approvalStatus", sortable: true, visible: true, width: 150 },
|
||||
{ title: "تاییدکننده", value: "approvedBy", sortable: true, visible: true, width: 120 },
|
||||
{ title: "تخفیف", value: "discountAll", sortable: true, visible: true, width: 120 },
|
||||
{ title: "حمل و نقل", value: "transferCost", sortable: true, visible: true, width: 120 },
|
||||
{ title: "مبلغ", value: "amount", sortable: true, visible: true, width: 150 },
|
||||
{ title: "پرداختی", value: "relatedDocsCount", sortable: true, visible: true, width: 150 },
|
||||
{ title: "برچسب", value: "label", sortable: true, visible: true, width: 120 },
|
||||
{ title: "شرح", value: "des", sortable: true, visible: true, minWidth: 200 },
|
||||
],
|
||||
tableHeight: 500,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visibleHeaders() {
|
||||
return this.allHeaders.filter(header => {
|
||||
if ((header.value === 'approvalStatus' || header.value === 'approvedBy') && !this.business.requireTwoStepApproval) {
|
||||
return false;
|
||||
}
|
||||
return header.visible;
|
||||
});
|
||||
},
|
||||
paperSizes: [
|
||||
{ title: 'A4 عمودی', value: 'A4' },
|
||||
{ title: 'A4 افقی', value: 'A4-L' },
|
||||
{ title: 'A5 عمودی', value: 'A5' },
|
||||
{ title: 'A5 افقی', value: 'A5-L' },
|
||||
],
|
||||
sumSelected: 0,
|
||||
sumTotal: 0,
|
||||
itemsSelected: [],
|
||||
searchValue: '',
|
||||
types: [],
|
||||
loading: ref(true),
|
||||
items: [],
|
||||
orgItems: [],
|
||||
headers: [
|
||||
{ text: "عملیات", value: "operation" },
|
||||
{ text: "فاکتور", value: "code", sortable: true },
|
||||
{ text: "تاریخ", value: "date", sortable: true },
|
||||
{ text: "خریدار", value: "person", sortable: true },
|
||||
{ text: "تخفیف", value: "discountAll", sortable: true },
|
||||
{ text: "حمل و نقل", value: "transferCost", sortable: true },
|
||||
{ text: "مبلغ", value: "amount", sortable: true },
|
||||
{ text: "پرداختی", value: "relatedDocsCount", sortable: true },
|
||||
{ text: "برچسب", value: "label", width: 100 },
|
||||
{ text: "شرح", value: "des", sortable: true },
|
||||
]
|
||||
}),
|
||||
tableHeight() {
|
||||
return window.innerHeight - 200;
|
||||
},
|
||||
displayItems() {
|
||||
if (!this.business.requireTwoStepApproval) return this.items;
|
||||
return this.currentTab === 'pending' ? this.itemsPending : this.itemsApproved;
|
||||
},
|
||||
displayTotal() {
|
||||
if (!this.business.requireTwoStepApproval) return this.total;
|
||||
return this.currentTab === 'pending' ? this.totalPending : this.totalApproved;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openPrintModal(code) {
|
||||
this.printOptions.selectedPrintCode = code;
|
||||
|
@ -293,7 +361,7 @@ export default {
|
|||
} else {
|
||||
this.loading = true;
|
||||
axios.post('/api/buy/label/change', {
|
||||
'items': this.itemsSelected,
|
||||
'items': this.itemsSelected.filter(item => item && typeof item === 'string').map(item => ({ code: item })),
|
||||
'label': label
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
|
@ -315,6 +383,130 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
checkApprover() {
|
||||
return this.business.requireTwoStepApproval && (this.business.approvers.buyInvoice == this.currentUser.email || this.currentUser.owner === true);
|
||||
},
|
||||
async loadBusinessInfo() {
|
||||
try {
|
||||
const response = await axios.get('/api/business/get/info/' + localStorage.getItem('activeBid'));
|
||||
this.business = response.data || { requireTwoStepApproval: false, approvers: { buyInvoice: null } };
|
||||
} catch (error) {
|
||||
console.error('Error loading business info:', error);
|
||||
this.business = { requireTwoStepApproval: false, approvers: { buyInvoice: null } };
|
||||
}
|
||||
},
|
||||
async loadCurrentUser() {
|
||||
try {
|
||||
const response = await axios.post('/api/business/get/user/permissions');
|
||||
this.currentUser = response.data || { email: '', owner: false };
|
||||
} catch (error) {
|
||||
console.error('Error loading current user:', error);
|
||||
this.currentUser = { email: '', owner: false };
|
||||
}
|
||||
},
|
||||
getApprovalStatusText(item) {
|
||||
if (!item || !this.business?.requireTwoStepApproval) return 'تایید دو مرحلهای غیرفعال';
|
||||
if (item.isPreview) return 'در انتظار تایید';
|
||||
if (item.isApproved) return 'تایید شده';
|
||||
return 'تایید شده';
|
||||
},
|
||||
getApprovalStatusColor(item) {
|
||||
if (!item || !this.business?.requireTwoStepApproval) return 'default';
|
||||
if (item.isPreview) return 'warning';
|
||||
if (item.isApproved) return 'success';
|
||||
return 'success';
|
||||
},
|
||||
canShowApprovalButton(item) {
|
||||
if (!this.checkApprover()) return false;
|
||||
if (item?.isApproved) return false;
|
||||
return true;
|
||||
},
|
||||
async approveInvoice(code) {
|
||||
try {
|
||||
this.loading = true;
|
||||
await axios.post(`/api/approval/approve/buy/${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;
|
||||
}
|
||||
},
|
||||
canShowUnapproveButton(item) {
|
||||
return !this.canShowApprovalButton(item) && this.checkApprover();
|
||||
},
|
||||
async unapproveInvoice(code) {
|
||||
try {
|
||||
this.loading = true;
|
||||
await axios.post(`/api/approval/unapprove/buy/${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;
|
||||
}
|
||||
},
|
||||
getItemByCode(code) {
|
||||
return this.items.find(item => item.code === code);
|
||||
},
|
||||
approveSelectedInvoices() {
|
||||
if (this.itemsSelected.length === 0) {
|
||||
Swal.fire({ text: 'هیچ موردی انتخاب نشده است.', icon: 'warning', confirmButtonText: 'قبول' });
|
||||
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: 'خیر' })
|
||||
.then(async (r) => {
|
||||
if (!r.isConfirmed) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
await axios.post(`/api/approval/approve/group/buy`, {
|
||||
'docIds': this.itemsSelected.filter(item => item && typeof item === 'string')
|
||||
});
|
||||
Swal.fire({ text: 'فاکتورها تایید شدند.', icon: 'success', confirmButtonText: 'قبول' });
|
||||
this.itemsSelected = [];
|
||||
this.loadData();
|
||||
} catch (e) {
|
||||
Swal.fire({ text: 'خطا در تایید فاکتورها', icon: 'error', confirmButtonText: 'قبول' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
unapproveSelectedInvoices() {
|
||||
if (this.itemsSelected.length === 0) {
|
||||
Swal.fire({ text: 'هیچ موردی انتخاب نشده است.', icon: 'warning', confirmButtonText: 'قبول' });
|
||||
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: 'خیر' })
|
||||
.then(async (r) => {
|
||||
if (!r.isConfirmed) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
await axios.post(`/api/approval/unapprove/group/buy`, {
|
||||
'docIds': this.itemsSelected.filter(item => item && typeof item === 'string')
|
||||
});
|
||||
Swal.fire({ text: 'تایید فاکتورها لغو شد.', icon: 'success', confirmButtonText: 'قبول' });
|
||||
this.itemsSelected = [];
|
||||
this.loadData();
|
||||
} catch (e) {
|
||||
Swal.fire({ text: 'خطا در لغو تایید فاکتورها', icon: 'error', confirmButtonText: 'قبول' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
filterTable() {
|
||||
this.loading = true;
|
||||
let calcItems = [];
|
||||
|
@ -350,27 +542,70 @@ export default {
|
|||
}
|
||||
this.loading = false;
|
||||
},
|
||||
loadData() {
|
||||
axios.post("/api/printers/options/info").then((response) => {
|
||||
this.printOptions = response.data.buy;
|
||||
});
|
||||
updateServerOptions(options) {
|
||||
this.serverOptions.page = options.page;
|
||||
this.serverOptions.rowsPerPage = options.itemsPerPage;
|
||||
this.serverOptions.sortBy = options.sortBy || [];
|
||||
this.loadData();
|
||||
},
|
||||
debouncedLoadData: debounce(function () {
|
||||
this.loadData();
|
||||
}, 300),
|
||||
|
||||
axios.post('/api/invoice/types', {
|
||||
type: 'buy'
|
||||
}).then((response) => {
|
||||
this.types = response.data;
|
||||
});
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
if (!this.printOptions.selectedPrintCode) {
|
||||
const printResponse = await axios.post("/api/printers/options/info");
|
||||
this.printOptions = printResponse.data.buy || this.printOptions;
|
||||
}
|
||||
|
||||
axios.post('/api/buy/docs/search', {
|
||||
type: 'buy'
|
||||
}).then((response) => {
|
||||
this.items = response.data;
|
||||
this.orgItems = response.data;
|
||||
this.items.forEach((item) => {
|
||||
this.sumTotal += parseInt(item.amount);
|
||||
const typesResponse = await axios.post('/api/invoice/types', { type: 'buy' });
|
||||
this.types = typesResponse.data.map(t => ({
|
||||
...t,
|
||||
checked: this.types.find(x => x.code === t.code)?.checked ?? false
|
||||
}));
|
||||
|
||||
const response = await axios.post('/api/buy/docs/search', {
|
||||
type: 'buy',
|
||||
search: this.searchValue,
|
||||
page: this.serverOptions.page,
|
||||
perPage: this.serverOptions.rowsPerPage,
|
||||
types: this.types.filter(t => t.checked).map(t => t.code),
|
||||
sortBy: this.serverOptions.sortBy,
|
||||
});
|
||||
|
||||
const all = (response.data.items || []).map(item => ({
|
||||
...item,
|
||||
approvalStatus: this.business.requireTwoStepApproval ?
|
||||
(item.isPreview ? 'pending' : 'approved') : 'disabled',
|
||||
approvedBy: item.approvedBy || null
|
||||
})).filter(item => item.code && typeof item.code !== 'undefined');
|
||||
|
||||
this.items = all;
|
||||
this.total = Number(response.data.total) || 0;
|
||||
|
||||
if (this.business.requireTwoStepApproval) {
|
||||
this.itemsApproved = all.filter(i => i.isApproved === true);
|
||||
this.itemsPending = all.filter(i => i.isPreview === true && i.isApproved !== true);
|
||||
this.totalApproved = this.itemsApproved.length;
|
||||
this.totalPending = this.itemsPending.length;
|
||||
}
|
||||
|
||||
this.sumTotal = this.displayItems.reduce((sum, item) => sum + parseInt(item.amount || 0), 0);
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
this.items = [];
|
||||
this.total = 0;
|
||||
this.sumTotal = 0;
|
||||
Swal.fire({
|
||||
text: 'خطا در بارگذاری دادهها: ' + error.message,
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
canEditItem(code) {
|
||||
this.loading = true;
|
||||
|
@ -405,7 +640,7 @@ export default {
|
|||
if (result.isConfirmed) {
|
||||
this.loading = true;
|
||||
axios.post('/api/accounting/remove/group', {
|
||||
'items': this.itemsSelected
|
||||
'items': this.itemsSelected.filter(item => item && typeof item === 'string')
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
if (response.data.result == 1) {
|
||||
|
@ -474,55 +709,53 @@ export default {
|
|||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
updateServerOptions(options) {
|
||||
this.serverOptions.page = options.page;
|
||||
this.serverOptions.rowsPerPage = options.itemsPerPage;
|
||||
this.serverOptions.sortBy = options.sortBy || [];
|
||||
this.loadData();
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
async beforeMount() {
|
||||
await this.loadBusinessInfo();
|
||||
await this.loadCurrentUser();
|
||||
this.loadData();
|
||||
},
|
||||
watch: {
|
||||
itemsSelected: {
|
||||
handler: function (val) {
|
||||
this.sumSelected = 0;
|
||||
this.itemsSelected.forEach((item) => {
|
||||
if (typeof item.amount.valueOf() === "string") {
|
||||
this.sumSelected += parseInt(item.amount.replaceAll(",", ""));
|
||||
} else {
|
||||
this.sumSelected += item.amount;
|
||||
this.itemsSelected.forEach((code) => {
|
||||
if (code && typeof code === 'string') {
|
||||
const item = this.items.find(item => item.code === code);
|
||||
if (item && item.amount) {
|
||||
if (typeof item.amount === "string") {
|
||||
this.sumSelected += parseInt(item.amount.replaceAll(",", ""));
|
||||
} else {
|
||||
this.sumSelected += item.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
searchValue: {
|
||||
handler() {
|
||||
this.serverOptions.page = 1;
|
||||
this.debouncedLoadData();
|
||||
},
|
||||
immediate: false,
|
||||
},
|
||||
currentTab: {
|
||||
handler: function (val) {
|
||||
if (this.searchValue == '') {
|
||||
this.items = this.orgItems;
|
||||
} else {
|
||||
let temp = [];
|
||||
this.orgItems.forEach((item) => {
|
||||
if (item.person.nikename.includes(this.searchValue)) {
|
||||
temp.push(item);
|
||||
} else if (item.date.includes(this.searchValue)) {
|
||||
temp.push(item);
|
||||
} else if (item.amount.toString().includes(this.searchValue)) {
|
||||
temp.push(item);
|
||||
} else if (item.des.includes(this.searchValue)) {
|
||||
temp.push(item);
|
||||
} else if (item.code.includes(this.searchValue)) {
|
||||
temp.push(item);
|
||||
} else if (item.label) {
|
||||
if (item.label.label.includes(this.searchValue)) {
|
||||
temp.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.items = temp;
|
||||
}
|
||||
this.loadData();
|
||||
},
|
||||
deep: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -506,6 +506,7 @@ export default {
|
|||
bid: {
|
||||
maliyatafzode: 0
|
||||
},
|
||||
business: { requireTwoStepApproval: false, approvers: { buyInvoice: null } },
|
||||
desSubmit: {
|
||||
id: '',
|
||||
des: ''
|
||||
|
@ -871,6 +872,7 @@ export default {
|
|||
//load business info
|
||||
axios.post('/api/business/get/info/' + localStorage.getItem('activeBid')).then((response) => {
|
||||
this.bid = response.data;
|
||||
this.business = response.data || { requireTwoStepApproval: false, approvers: { buyInvoice: null } };
|
||||
if (this.bid.maliyatafzode == 0) {
|
||||
this.maliyatCheck = false;
|
||||
}
|
||||
|
@ -964,7 +966,8 @@ export default {
|
|||
rows: this.items,
|
||||
discountAll: this.data.discountAll,
|
||||
transferCost: this.data.transferCost,
|
||||
update: this.$route.params.id
|
||||
update: this.$route.params.id,
|
||||
business: this.business
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
if (response.data.code == 0) {
|
||||
|
|
|
@ -57,6 +57,7 @@ export default defineComponent({
|
|||
bid: {
|
||||
legal_name: '',
|
||||
},
|
||||
business: { requireTwoStepApproval: false, approvers: { buyInvoice: null } },
|
||||
item: {
|
||||
doc: {
|
||||
id: 0,
|
||||
|
@ -90,6 +91,18 @@ export default defineComponent({
|
|||
navigator.clipboard.writeText(this.shortlink_url);
|
||||
this.copy_label = 'کپی شد !';
|
||||
},
|
||||
getApprovalStatusText(item) {
|
||||
if (!this.business?.requireTwoStepApproval) return 'تایید دو مرحلهای غیرفعال';
|
||||
if (item.isPreview) return 'در انتظار تایید';
|
||||
if (item.isApproved) return 'تایید شده';
|
||||
return 'تایید شده';
|
||||
},
|
||||
getApprovalStatusColor(item) {
|
||||
if (!this.business?.requireTwoStepApproval) return 'default';
|
||||
if (item.isPreview) return 'warning';
|
||||
if (item.isApproved) return 'success';
|
||||
return 'success';
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
this.commoditys = [];
|
||||
|
@ -127,6 +140,7 @@ export default defineComponent({
|
|||
});
|
||||
});
|
||||
axios.post('/api/business/get/info/' + localStorage.getItem('activeBid')).then((response) => {
|
||||
this.business = response.data || { requireTwoStepApproval: false, approvers: { buyInvoice: null } };
|
||||
this.bid = response.data;
|
||||
});
|
||||
axios.post("/api/printers/options/info").then((response) => {
|
||||
|
@ -220,6 +234,11 @@ export default defineComponent({
|
|||
</button>
|
||||
<i class="fas fa-file-invoice-dollar"></i>
|
||||
مشاهده فاکتور
|
||||
<span v-if="business.requireTwoStepApproval" class="ms-2">
|
||||
<v-chip :color="getApprovalStatusColor(item.doc)" size="small">
|
||||
{{ getApprovalStatusText(item.doc) }}
|
||||
</v-chip>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="block-options">
|
||||
<archive-upload v-if="this.item.doc.id != 0" :docid="this.item.doc.id" doctype="buy" cat="buy"></archive-upload>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
</v-toolbar>
|
||||
<!-- Tabs for two-step approval -->
|
||||
<div v-if="business.requireTwoStepApproval" class="px-2 pt-2">
|
||||
<v-tabs v-model="currentTab" color="primary" density="comfortable">
|
||||
<v-tabs v-model="currentTab" color="primary" density="comfortable" grow>
|
||||
<v-tab value="approved">فاکتورهای تایید شده</v-tab>
|
||||
<v-tab value="pending">فاکتورهای در انتظار تایید</v-tab>
|
||||
</v-tabs>
|
||||
|
|
Loading…
Reference in a new issue