almost finish direc document insert and redesign storeroom part
This commit is contained in:
parent
04550d2171
commit
c1dc79da52
|
@ -59,6 +59,58 @@ class BankController extends AbstractController
|
||||||
return $this->json($provider->ArrayEntity2Array($datas, 0));
|
return $this->json($provider->ArrayEntity2Array($datas, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/bank/search', name: 'app_bank_search')]
|
||||||
|
public function app_bank_search(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('banks');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
if ($content = $request->getContent()) {
|
||||||
|
$params = json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $entityManager->createQueryBuilder()
|
||||||
|
->select('b')
|
||||||
|
->from(BankAccount::class, 'b')
|
||||||
|
->where('b.bid = :bid')
|
||||||
|
->andWhere('b.money = :money')
|
||||||
|
->setParameter('bid', $acc['bid'])
|
||||||
|
->setParameter('money', $acc['money']);
|
||||||
|
|
||||||
|
if (isset($params['search']) && !empty($params['search'])) {
|
||||||
|
$query->andWhere('b.name LIKE :search')
|
||||||
|
->setParameter('search', '%' . $params['search'] . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['page']) && isset($params['itemsPerPage'])) {
|
||||||
|
$query->setFirstResult(($params['page'] - 1) * $params['itemsPerPage'])
|
||||||
|
->setMaxResults($params['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$datas = $query->getQuery()->getResult();
|
||||||
|
|
||||||
|
// محاسبه موجودی برای هر حساب
|
||||||
|
foreach ($datas as $data) {
|
||||||
|
$bs = 0;
|
||||||
|
$bd = 0;
|
||||||
|
$items = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||||
|
'bank' => $data
|
||||||
|
]);
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$bs += $item->getBs();
|
||||||
|
$bd += $item->getBd();
|
||||||
|
}
|
||||||
|
$data->setBalance($bd - $bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'items' => $provider->ArrayEntity2Array($datas, 0),
|
||||||
|
'total' => count($datas)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/api/bank/info/{code}', name: 'app_bank_info')]
|
#[Route('/api/bank/info/{code}', name: 'app_bank_info')]
|
||||||
public function app_bank_info($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
public function app_bank_info($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
{
|
{
|
||||||
|
@ -147,6 +199,88 @@ class BankController extends AbstractController
|
||||||
return $this->json(['result' => 1]);
|
return $this->json(['result' => 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/bank/balance/{code}', name: 'app_bank_balance')]
|
||||||
|
public function app_bank_balance($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('banks');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$bank = $entityManager->getRepository(BankAccount::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'money' => $acc['money'],
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$bank)
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
|
||||||
|
$bs = 0;
|
||||||
|
$bd = 0;
|
||||||
|
$items = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||||
|
'bank' => $bank
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$bs += $item->getBs();
|
||||||
|
$bd += $item->getBd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'balance' => $bd - $bs,
|
||||||
|
'debit' => $bd,
|
||||||
|
'credit' => $bs
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/bank/transactions/{code}', name: 'app_bank_transactions')]
|
||||||
|
public function app_bank_transactions($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('banks');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
if ($content = $request->getContent()) {
|
||||||
|
$params = json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bank = $entityManager->getRepository(BankAccount::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'money' => $acc['money'],
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$bank)
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
|
||||||
|
$query = $entityManager->createQueryBuilder()
|
||||||
|
->select('r')
|
||||||
|
->from(HesabdariRow::class, 'r')
|
||||||
|
->where('r.bank = :bank')
|
||||||
|
->andWhere('r.bid = :bid')
|
||||||
|
->setParameter('bank', $bank)
|
||||||
|
->setParameter('bid', $acc['bid']);
|
||||||
|
|
||||||
|
if (isset($params['startDate']) && isset($params['endDate'])) {
|
||||||
|
$query->andWhere('r.doc.date BETWEEN :startDate AND :endDate')
|
||||||
|
->setParameter('startDate', $params['startDate'])
|
||||||
|
->setParameter('endDate', $params['endDate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['page']) && isset($params['itemsPerPage'])) {
|
||||||
|
$query->setFirstResult(($params['page'] - 1) * $params['itemsPerPage'])
|
||||||
|
->setMaxResults($params['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = $query->getQuery()->getResult();
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'items' => $provider->ArrayEntity2Array($transactions, 0),
|
||||||
|
'total' => count($transactions)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -135,4 +135,137 @@ class CashdeskController extends AbstractController
|
||||||
$log->insert('بانکداری', ' صندوق با نام ' . $name . ' حذف شد. ', $this->getUser(), $acc['bid']->getId());
|
$log->insert('بانکداری', ' صندوق با نام ' . $name . ' حذف شد. ', $this->getUser(), $acc['bid']->getId());
|
||||||
return $this->json(['result' => 1]);
|
return $this->json(['result' => 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/cashdesk/search', name: 'app_cashdesk_search')]
|
||||||
|
public function app_cashdesk_search(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('cashdesk');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
if ($content = $request->getContent()) {
|
||||||
|
$params = json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $entityManager->createQueryBuilder()
|
||||||
|
->select('c')
|
||||||
|
->from(Cashdesk::class, 'c')
|
||||||
|
->where('c.bid = :bid')
|
||||||
|
->andWhere('c.money = :money')
|
||||||
|
->setParameter('bid', $acc['bid'])
|
||||||
|
->setParameter('money', $acc['money']);
|
||||||
|
|
||||||
|
if (isset($params['search']) && !empty($params['search'])) {
|
||||||
|
$query->andWhere('c.name LIKE :search')
|
||||||
|
->setParameter('search', '%' . $params['search'] . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['page']) && isset($params['itemsPerPage'])) {
|
||||||
|
$query->setFirstResult(($params['page'] - 1) * $params['itemsPerPage'])
|
||||||
|
->setMaxResults($params['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$datas = $query->getQuery()->getResult();
|
||||||
|
|
||||||
|
foreach ($datas as $data) {
|
||||||
|
$bs = 0;
|
||||||
|
$bd = 0;
|
||||||
|
$items = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||||
|
'cashdesk' => $data
|
||||||
|
]);
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$bs += $item->getBs();
|
||||||
|
$bd += $item->getBd();
|
||||||
|
}
|
||||||
|
$data->setBalance($bd - $bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'items' => $provider->ArrayEntity2Array($datas, 0),
|
||||||
|
'total' => count($datas)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/cashdesk/balance/{code}', name: 'app_cashdesk_balance')]
|
||||||
|
public function app_cashdesk_balance($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('cashdesk');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$cashdesk = $entityManager->getRepository(Cashdesk::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'money' => $acc['money'],
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$cashdesk)
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
|
||||||
|
$bs = 0;
|
||||||
|
$bd = 0;
|
||||||
|
$items = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||||
|
'cashdesk' => $cashdesk
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$bs += $item->getBs();
|
||||||
|
$bd += $item->getBd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'balance' => $bd - $bs,
|
||||||
|
'debit' => $bd,
|
||||||
|
'credit' => $bs
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/cashdesk/transactions/{code}', name: 'app_cashdesk_transactions')]
|
||||||
|
public function app_cashdesk_transactions($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('cashdesk');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
if ($content = $request->getContent()) {
|
||||||
|
$params = json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cashdesk = $entityManager->getRepository(Cashdesk::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'money' => $acc['money'],
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$cashdesk)
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
|
||||||
|
$query = $entityManager->createQueryBuilder()
|
||||||
|
->select('r')
|
||||||
|
->from(HesabdariRow::class, 'r')
|
||||||
|
->where('r.cashdesk = :cashdesk')
|
||||||
|
->andWhere('r.bid = :bid')
|
||||||
|
->setParameter('cashdesk', $cashdesk)
|
||||||
|
->setParameter('bid', $acc['bid']);
|
||||||
|
|
||||||
|
if (isset($params['startDate']) && isset($params['endDate'])) {
|
||||||
|
$query->andWhere('r.doc.date BETWEEN :startDate AND :endDate')
|
||||||
|
->setParameter('startDate', $params['startDate'])
|
||||||
|
->setParameter('endDate', $params['endDate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['page']) && isset($params['itemsPerPage'])) {
|
||||||
|
$query->setFirstResult(($params['page'] - 1) * $params['itemsPerPage'])
|
||||||
|
->setMaxResults($params['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = $query->getQuery()->getResult();
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'items' => $provider->ArrayEntity2Array($transactions, 0),
|
||||||
|
'total' => count($transactions)
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
33
hesabixCore/src/Controller/Componenets/BankController.php
Normal file
33
hesabixCore/src/Controller/Componenets/BankController.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Componenets;
|
||||||
|
|
||||||
|
use App\Entity\BankAccount;
|
||||||
|
use App\Service\Access;
|
||||||
|
use App\Service\Explore;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class BankController extends AbstractController
|
||||||
|
{
|
||||||
|
|
||||||
|
#[Route('/api/componenets/bank/get/{id}', name: 'app_componenets_bank_get')]
|
||||||
|
public function app_componenets_bank_get(Access $access,EntityManagerInterface $entityManager, $id): JsonResponse
|
||||||
|
{
|
||||||
|
$bank = $entityManager->getRepository(BankAccount::class)->find($id);
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
return new JsonResponse(['message' => 'Access denied'], Response::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
if (!$bank) {
|
||||||
|
return new JsonResponse(['message' => 'Bank not found'], Response::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
if($bank->getBid() != $acc['bid']){
|
||||||
|
return new JsonResponse(['message' => 'Access denied'], Response::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
return new JsonResponse(Explore::ExploreBank($bank));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Componenets;
|
||||||
|
|
||||||
|
use App\Entity\Cashdesk;
|
||||||
|
use App\Service\Access;
|
||||||
|
use App\Service\Explore;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class CashdeskController extends AbstractController
|
||||||
|
{
|
||||||
|
|
||||||
|
#[Route('/api/componenets/cashdesk/get/{id}', name: 'app_componenets_cashdesk_get')]
|
||||||
|
public function app_componenets_cashdesk_get(Access $access,EntityManagerInterface $entityManager, $id): JsonResponse
|
||||||
|
{
|
||||||
|
$cashdesk = $entityManager->getRepository(Cashdesk::class)->find($id);
|
||||||
|
$acc = $access->hasRole('join');
|
||||||
|
if (!$acc) {
|
||||||
|
return new JsonResponse(['message' => 'Access denied'], Response::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
if (!$cashdesk) {
|
||||||
|
return new JsonResponse(['message' => 'Cashdesk not found'], Response::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
if($cashdesk->getBid() != $acc['bid']){
|
||||||
|
return new JsonResponse(['message' => 'Access denied'], Response::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
return new JsonResponse(Explore::ExploreCashdesk($cashdesk));
|
||||||
|
}
|
||||||
|
}
|
358
hesabixCore/src/Controller/DirectHesabdariDoc.php
Normal file
358
hesabixCore/src/Controller/DirectHesabdariDoc.php
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\BankAccount;
|
||||||
|
use App\Entity\Cashdesk;
|
||||||
|
use App\Entity\Commodity;
|
||||||
|
use App\Entity\HesabdariRow;
|
||||||
|
use App\Entity\HesabdariTable;
|
||||||
|
use App\Entity\HesabdariDoc;
|
||||||
|
use App\Entity\Person;
|
||||||
|
use App\Entity\Salary;
|
||||||
|
use App\Service\Access;
|
||||||
|
use App\Service\Log;
|
||||||
|
use App\Service\Provider;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
class DirectHesabdariDoc extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/api/hesabdari/direct/doc/create', name: 'create_hesabdari_doc_insert')]
|
||||||
|
public function create(Log $log, Access $access, Request $request, Provider $provider, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('accounting');
|
||||||
|
if (!$acc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$prams = $request->getPayload()->all();
|
||||||
|
|
||||||
|
$hesabdariDoc = new HesabdariDoc();
|
||||||
|
$hesabdariDoc->setType('calc');
|
||||||
|
$hesabdariDoc->setBid($acc['bid']);
|
||||||
|
$hesabdariDoc->setSubmitter($this->getUser());
|
||||||
|
$hesabdariDoc->setDes($prams['des']);
|
||||||
|
$hesabdariDoc->setYear($acc['year']);
|
||||||
|
$hesabdariDoc->setMoney($acc['money']);
|
||||||
|
$hesabdariDoc->setDate($prams['date']);
|
||||||
|
$hesabdariDoc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||||
|
$hesabdariDoc->setDateSubmit(time());
|
||||||
|
|
||||||
|
//insert rows
|
||||||
|
if (isset($prams['rows'])) {
|
||||||
|
if (count($prams['rows']) < 2) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حداقل باید دو سطر در سند وجود داشته باشد'], 400);
|
||||||
|
}
|
||||||
|
$totalBs = 0;
|
||||||
|
foreach ($prams['rows'] as $row) {
|
||||||
|
$hesabdariRow = new HesabdariRow();
|
||||||
|
$hesabdariRow->setDoc($hesabdariDoc);
|
||||||
|
$hesabdariRow->setBs($row['bs']);
|
||||||
|
$hesabdariRow->setBd($row['bd']);
|
||||||
|
$hesabdariRow->setDes($row['des']);
|
||||||
|
$hesabdariRow->setYear($acc['year']);
|
||||||
|
$hesabdariRow->setRefData($row['detail']);
|
||||||
|
$hesabdariRow->setBid($acc['bid']);
|
||||||
|
$totalBs += floatval($row['bs']);
|
||||||
|
//get ref
|
||||||
|
$ref = $entityManager->getRepository(HesabdariTable::class)->find($row['ref']);
|
||||||
|
if ($ref) {
|
||||||
|
if ($ref->getBid() == $acc['bid'] || $ref->getBid() == null) {
|
||||||
|
$hesabdariRow->setRef($ref);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به حساب'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حساب مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['bankAccount']) {
|
||||||
|
$bankAccount = $entityManager->getRepository(BankAccount::class)->find($row['bankAccount']);
|
||||||
|
if ($bankAccount) {
|
||||||
|
if ($bankAccount->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setBank($bankAccount);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به حساب بانکی'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حساب بانکی مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($row['cashdesk']) {
|
||||||
|
$cashdesk = $entityManager->getRepository(Cashdesk::class)->find($row['cashdesk']);
|
||||||
|
if ($cashdesk) {
|
||||||
|
if ($cashdesk->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setCashDesk($cashdesk);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به صندوق'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'صندوق مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['salary']) {
|
||||||
|
$salary = $entityManager->getRepository(Salary::class)->find($row['salary']);
|
||||||
|
if ($salary) {
|
||||||
|
if ($salary->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setSalary($salary);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به حقوق'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حقوق مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['person']) {
|
||||||
|
$person = $entityManager->getRepository(Person::class)->find($row['person']);
|
||||||
|
if ($person) {
|
||||||
|
if ($person->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setPerson($person);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به شخص'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'شخص مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['commodity'] && $row['commodityCount']) {
|
||||||
|
if (!is_numeric($row['commodityCount']) || $row['commodityCount'] <= 0) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'تعداد کالا باید عددی مثبت باشد'], 400);
|
||||||
|
}
|
||||||
|
$commodity = $entityManager->getRepository(Commodity::class)->find($row['commodity']);
|
||||||
|
if ($commodity) {
|
||||||
|
if ($commodity->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setCommodity($commodity);
|
||||||
|
$hesabdariRow->setCommdityCount($row['commodityCount']);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به کالا'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'کالای مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$entityManager->persist($hesabdariRow);
|
||||||
|
}
|
||||||
|
$hesabdariDoc->setAmount($totalBs);
|
||||||
|
}
|
||||||
|
$entityManager->persist($hesabdariDoc);
|
||||||
|
$entityManager->flush();
|
||||||
|
$log->insert('حسابداری', 'ایجاد سند حسابداری شماره ' . $hesabdariDoc->getCode(), $this->getUser(), $acc['bid'], $hesabdariDoc);
|
||||||
|
return new JsonResponse(['success' => true, 'message' => 'سند با موفقیت ثبت شد', 'data' => ['id' => $hesabdariDoc->getId()]], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/hesabdari/direct/doc/update/{id}', name: 'update_hesabdari_doc_update')]
|
||||||
|
public function update(Log $log, Access $access, Request $request, int $id, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('accounting');
|
||||||
|
if (!$acc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hesabdariDoc = $entityManager->getRepository(HesabdariDoc::class)->find($id);
|
||||||
|
if (!$hesabdariDoc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'سند مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hesabdariDoc->getBid() !== $acc['bid']) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به سند'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$prams = $request->getPayload()->all();
|
||||||
|
|
||||||
|
$hesabdariDoc->setDes($prams['des']);
|
||||||
|
$hesabdariDoc->setDate($prams['date']);
|
||||||
|
|
||||||
|
// حذف ردیفهای قبلی
|
||||||
|
foreach ($hesabdariDoc->getHesabdariRows() as $row) {
|
||||||
|
$entityManager->remove($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// اضافه کردن ردیفهای جدید
|
||||||
|
if (isset($prams['rows'])) {
|
||||||
|
if (count($prams['rows']) < 2) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حداقل باید دو سطر در سند وجود داشته باشد'], 400);
|
||||||
|
}
|
||||||
|
$totalBs = 0;
|
||||||
|
foreach ($prams['rows'] as $row) {
|
||||||
|
$hesabdariRow = new HesabdariRow();
|
||||||
|
$hesabdariRow->setDoc($hesabdariDoc);
|
||||||
|
$hesabdariRow->setBs($row['bs']);
|
||||||
|
$hesabdariRow->setBd($row['bd']);
|
||||||
|
$hesabdariRow->setDes($row['des']);
|
||||||
|
$hesabdariRow->setYear($acc['year']);
|
||||||
|
$hesabdariRow->setRefData($row['detail']);
|
||||||
|
$hesabdariRow->setBid($acc['bid']);
|
||||||
|
$totalBs += floatval($row['bs']);
|
||||||
|
//get ref
|
||||||
|
$ref = $entityManager->getRepository(HesabdariTable::class)->find($row['ref']);
|
||||||
|
if ($ref) {
|
||||||
|
if ($ref->getBid() == $acc['bid'] || $ref->getBid() == null) {
|
||||||
|
$hesabdariRow->setRef($ref);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به حساب'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حساب مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['bankAccount']) {
|
||||||
|
$bankAccount = $entityManager->getRepository(BankAccount::class)->find($row['bankAccount']);
|
||||||
|
if ($bankAccount) {
|
||||||
|
if ($bankAccount->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setBank($bankAccount);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به حساب بانکی'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حساب بانکی مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['cashdesk']) {
|
||||||
|
$cashdesk = $entityManager->getRepository(Cashdesk::class)->find($row['cashdesk']);
|
||||||
|
if ($cashdesk) {
|
||||||
|
if ($cashdesk->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setCashDesk($cashdesk);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به صندوق'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'صندوق مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['salary']) {
|
||||||
|
$salary = $entityManager->getRepository(Salary::class)->find($row['salary']);
|
||||||
|
if ($salary) {
|
||||||
|
if ($salary->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setSalary($salary);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به حقوق'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'حقوق مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['person']) {
|
||||||
|
$person = $entityManager->getRepository(Person::class)->find($row['person']);
|
||||||
|
if ($person) {
|
||||||
|
if ($person->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setPerson($person);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به شخص'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'شخص مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['commodity'] && $row['commodityCount']) {
|
||||||
|
if (!is_numeric($row['commodityCount']) || $row['commodityCount'] <= 0) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'تعداد کالا باید عددی مثبت باشد'], 400);
|
||||||
|
}
|
||||||
|
$commodity = $entityManager->getRepository(Commodity::class)->find($row['commodity']);
|
||||||
|
if ($commodity) {
|
||||||
|
if ($commodity->getBid() == $acc['bid']) {
|
||||||
|
$hesabdariRow->setCommodity($commodity);
|
||||||
|
$hesabdariRow->setCommdityCount($row['commodityCount']);
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به کالا'], 403);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'کالای مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$entityManager->persist($hesabdariRow);
|
||||||
|
}
|
||||||
|
$hesabdariDoc->setAmount($totalBs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityManager->flush();
|
||||||
|
$log->insert('حسابداری', 'ویرایش سند حسابداری شماره ' . $hesabdariDoc->getCode(), $this->getUser(), $acc['bid'], $hesabdariDoc);
|
||||||
|
return new JsonResponse(['success' => true, 'message' => 'سند با موفقیت ویرایش شد'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/hesabdari/direct/doc/delete/{id}', name: 'delete_hesabdari_doc_delete')]
|
||||||
|
public function delete(Log $log, Access $access, int $id, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('accounting');
|
||||||
|
if (!$acc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hesabdariDoc = $entityManager->getRepository(HesabdariDoc::class)->find($id);
|
||||||
|
if (!$hesabdariDoc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'سند مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
if ($hesabdariDoc->getType() !== 'calc') {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'سند مورد نظر قابل حذف نیست'], 400);
|
||||||
|
}
|
||||||
|
if ($hesabdariDoc->getBid() !== $acc['bid']) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به سند'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityManager->remove($hesabdariDoc);
|
||||||
|
$entityManager->flush();
|
||||||
|
$log->insert('حسابداری', 'حذف سند حسابداری شماره ' . $hesabdariDoc->getCode(), $this->getUser(), $acc['bid'], $hesabdariDoc);
|
||||||
|
return new JsonResponse(['success' => true, 'message' => 'سند با موفقیت حذف شد'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/hesabdari/direct/doc/get/{id}', name: 'get_hesabdari_doc_get')]
|
||||||
|
public function get(Access $access, int $id, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('accounting');
|
||||||
|
if (!$acc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hesabdariDoc = $entityManager->getRepository(HesabdariDoc::class)->find($id);
|
||||||
|
if (!$hesabdariDoc) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'سند مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hesabdariDoc->getBid() !== $acc['bid']) {
|
||||||
|
return new JsonResponse(['success' => false, 'message' => 'دسترسی غیرمجاز به سند'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
foreach ($hesabdariDoc->getHesabdariRows() as $row) {
|
||||||
|
$rowData = [
|
||||||
|
'id' => $row->getId(),
|
||||||
|
'ref' => [
|
||||||
|
'id' => $row->getRef()->getId(),
|
||||||
|
'name' => $row->getRef()->getName(),
|
||||||
|
'tableType' => $row->getRef()->getType()
|
||||||
|
],
|
||||||
|
'bd' => $row->getBd(),
|
||||||
|
'bs' => $row->getBs(),
|
||||||
|
'des' => $row->getDes(),
|
||||||
|
'detail' => $row->getRefData(),
|
||||||
|
'bankAccount' => $row->getBank() ? $row->getBank()->getId() : null,
|
||||||
|
'cashdesk' => $row->getCashDesk() ? $row->getCashDesk()->getId() : null,
|
||||||
|
'salary' => $row->getSalary() ? $row->getSalary()->getId() : null,
|
||||||
|
'commodity' => $row->getCommodity() ? $row->getCommodity()->getId() : null,
|
||||||
|
'commodityCount' => $row->getCommdityCount(),
|
||||||
|
'person' => $row->getPerson() ? $row->getPerson()->getId() : null
|
||||||
|
];
|
||||||
|
$rows[] = $rowData;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $hesabdariDoc->getId(),
|
||||||
|
'date' => $hesabdariDoc->getDate(),
|
||||||
|
'des' => $hesabdariDoc->getDes(),
|
||||||
|
'code' => $hesabdariDoc->getCode(),
|
||||||
|
'rows' => $rows
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JsonResponse(['success' => true, 'data' => $data], 200);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1135,35 +1135,102 @@ class HesabdariController extends AbstractController
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/hesabdari/tables/{id}/children', name: 'get_hesabdari_table_children', methods: ['GET'])]
|
#[Route('/api/hesabdari/tables/tree', name: 'get_hesabdari_table_tree', methods: ['GET'])]
|
||||||
public function getHesabdariTableChildren(int $id, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
public function getHesabdariTableTree(Access $access, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$acc = $access->hasRole('accounting');
|
$acc = $access->hasRole('accounting');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
throw $this->createAccessDeniedException();
|
throw $this->createAccessDeniedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$node = $entityManager->getRepository(HesabdariTable::class)->find($id);
|
$depth = (int) $request->query->get('depth', 2); // عمق پیشفرض 2
|
||||||
if (!$node) {
|
$rootId = (int) $request->query->get('rootId', 1); // گره ریشه پیشفرض
|
||||||
return $this->json(['Success' => false, 'message' => 'نود مورد نظر یافت نشد'], 404);
|
|
||||||
|
$root = $entityManager->getRepository(HesabdariTable::class)->find($rootId);
|
||||||
|
if (!$root) {
|
||||||
|
return $this->json(['Success' => false, 'message' => 'نود ریشه یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$buildTree = function ($node, $depth, $currentDepth = 0) use ($entityManager, $acc, &$buildTree) {
|
||||||
|
if ($currentDepth >= $depth) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||||
'upper' => $node,
|
'upper' => $node,
|
||||||
'bid' => [$acc['bid']->getId(), null] // حسابهای عمومی و خصوصی
|
'bid' => [$acc['bid']->getId(), null],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($children as $child) {
|
foreach ($children as $child) {
|
||||||
$result[] = [
|
$childData = [
|
||||||
'id' => $child->getId(),
|
'id' => $child->getId(),
|
||||||
'name' => $child->getName(),
|
'name' => $child->getName(),
|
||||||
'code' => $child->getCode(),
|
'code' => $child->getCode(),
|
||||||
'type' => $child->getType(),
|
'type' => $child->getType(),
|
||||||
'children' => $this->hasChild($entityManager, $child) ? [] : null
|
'children' => $buildTree($child, $depth, $currentDepth + 1),
|
||||||
];
|
];
|
||||||
|
$result[] = $childData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->json(['Success' => true, 'data' => $result]);
|
return $result;
|
||||||
|
};
|
||||||
|
|
||||||
|
$tree = [
|
||||||
|
'id' => $root->getId(),
|
||||||
|
'name' => $root->getName(),
|
||||||
|
'code' => $root->getCode(),
|
||||||
|
'type' => $root->getType(),
|
||||||
|
'children' => $buildTree($root, $depth),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json(['Success' => true, 'data' => $tree]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/hesabdari/tables/all', name: 'get_all_hesabdari_tables', methods: ['GET'])]
|
||||||
|
public function getAllHesabdariTables(Access $access, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('accounting');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootId = (int) $request->query->get('rootId', 1); // گره ریشه پیشفرض
|
||||||
|
|
||||||
|
$root = $entityManager->getRepository(HesabdariTable::class)->find($rootId);
|
||||||
|
if (!$root) {
|
||||||
|
return $this->json(['Success' => false, 'message' => 'نود ریشه یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$buildTree = function ($node) use ($entityManager, $acc, &$buildTree) {
|
||||||
|
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||||
|
'upper' => $node,
|
||||||
|
'bid' => [$acc['bid']->getId(), null],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($children as $child) {
|
||||||
|
$childData = [
|
||||||
|
'id' => $child->getId(),
|
||||||
|
'name' => $child->getName(),
|
||||||
|
'code' => $child->getCode(),
|
||||||
|
'type' => $child->getType(),
|
||||||
|
'children' => $buildTree($child),
|
||||||
|
];
|
||||||
|
$result[] = $childData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
};
|
||||||
|
|
||||||
|
$tree = [
|
||||||
|
'id' => $root->getId(),
|
||||||
|
'name' => $root->getName(),
|
||||||
|
'code' => $root->getCode(),
|
||||||
|
'type' => $root->getType(),
|
||||||
|
'children' => $buildTree($root),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json(['Success' => true, 'data' => $tree]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,4 +134,137 @@ class SalaryController extends AbstractController
|
||||||
$log->insert('بانکداری', ' تنخواهگردان با نام ' . $name . ' حذف شد. ', $this->getUser(), $acc['bid']->getId());
|
$log->insert('بانکداری', ' تنخواهگردان با نام ' . $name . ' حذف شد. ', $this->getUser(), $acc['bid']->getId());
|
||||||
return $this->json(['result' => 1]);
|
return $this->json(['result' => 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/salary/search', name: 'app_salary_search')]
|
||||||
|
public function app_salary_search(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('salary');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
if ($content = $request->getContent()) {
|
||||||
|
$params = json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $entityManager->createQueryBuilder()
|
||||||
|
->select('s')
|
||||||
|
->from(Salary::class, 's')
|
||||||
|
->where('s.bid = :bid')
|
||||||
|
->andWhere('s.money = :money')
|
||||||
|
->setParameter('bid', $acc['bid'])
|
||||||
|
->setParameter('money', $acc['money']);
|
||||||
|
|
||||||
|
if (isset($params['search']) && !empty($params['search'])) {
|
||||||
|
$query->andWhere('s.name LIKE :search')
|
||||||
|
->setParameter('search', '%' . $params['search'] . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['page']) && isset($params['itemsPerPage'])) {
|
||||||
|
$query->setFirstResult(($params['page'] - 1) * $params['itemsPerPage'])
|
||||||
|
->setMaxResults($params['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$datas = $query->getQuery()->getResult();
|
||||||
|
|
||||||
|
foreach ($datas as $data) {
|
||||||
|
$bs = 0;
|
||||||
|
$bd = 0;
|
||||||
|
$items = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||||
|
'salary' => $data
|
||||||
|
]);
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$bs += $item->getBs();
|
||||||
|
$bd += $item->getBd();
|
||||||
|
}
|
||||||
|
$data->setBalance($bd - $bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'items' => $provider->ArrayEntity2Array($datas, 0),
|
||||||
|
'total' => count($datas)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/salary/balance/{code}', name: 'app_salary_balance')]
|
||||||
|
public function app_salary_balance($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('salary');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$salary = $entityManager->getRepository(Salary::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'money' => $acc['money'],
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$salary)
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
|
||||||
|
$bs = 0;
|
||||||
|
$bd = 0;
|
||||||
|
$items = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||||
|
'salary' => $salary
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$bs += $item->getBs();
|
||||||
|
$bd += $item->getBd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'balance' => $bd - $bs,
|
||||||
|
'debit' => $bd,
|
||||||
|
'credit' => $bs
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/salary/transactions/{code}', name: 'app_salary_transactions')]
|
||||||
|
public function app_salary_transactions($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('salary');
|
||||||
|
if (!$acc)
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
if ($content = $request->getContent()) {
|
||||||
|
$params = json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$salary = $entityManager->getRepository(Salary::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'money' => $acc['money'],
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$salary)
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
|
||||||
|
$query = $entityManager->createQueryBuilder()
|
||||||
|
->select('r')
|
||||||
|
->from(HesabdariRow::class, 'r')
|
||||||
|
->where('r.salary = :salary')
|
||||||
|
->andWhere('r.bid = :bid')
|
||||||
|
->setParameter('salary', $salary)
|
||||||
|
->setParameter('bid', $acc['bid']);
|
||||||
|
|
||||||
|
if (isset($params['startDate']) && isset($params['endDate'])) {
|
||||||
|
$query->andWhere('r.doc.date BETWEEN :startDate AND :endDate')
|
||||||
|
->setParameter('startDate', $params['startDate'])
|
||||||
|
->setParameter('endDate', $params['endDate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['page']) && isset($params['itemsPerPage'])) {
|
||||||
|
$query->setFirstResult(($params['page'] - 1) * $params['itemsPerPage'])
|
||||||
|
->setMaxResults($params['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = $query->getQuery()->getResult();
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'items' => $provider->ArrayEntity2Array($transactions, 0),
|
||||||
|
'total' => count($transactions)
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
{% extends 'pdf/base.html.twig' %}
|
{% extends 'pdf/base.html.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div style="width:100%;margin-top:20px;text-align:center;">
|
<div style="width:100%;margin-top:20px;text-align:center;border: 1px solid black;border-radius: 8px;">
|
||||||
<div style="width:100%;margin-top:5px;margin-bottom:10px;text-align:center;">
|
<div style="width:100%;margin-top:5px;margin-bottom:10px;text-align:center;">
|
||||||
<table style="width:100%; border-radius: 8px;">
|
<table style="width:100%;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr style="font-size:12px;">
|
<tr style="font-size:12px;">
|
||||||
<td class="right" style="border: 1px solid black;">
|
<td class="right">
|
||||||
<p>
|
<p>
|
||||||
<b>شماره سند:</b>
|
<b>شماره سند:</b>
|
||||||
{{ doc.code }}
|
{{ doc.code }}
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="right" style="border: 1px solid black;">
|
<td class="right">
|
||||||
<p>
|
<p>
|
||||||
<b>نوع سند:</b>
|
<b>نوع سند:</b>
|
||||||
{% if doc.type == 'cost' %}
|
{% if doc.type == 'cost' %}
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-size:12px;">
|
<tr style="font-size:12px;">
|
||||||
<td class="right" colspan="2" style="border: 1px solid black;">
|
<td class="right" colspan="2">
|
||||||
<p>
|
<p>
|
||||||
<b>توضیحات:</b>
|
<b>توضیحات:</b>
|
||||||
{{ doc.des }}
|
{{ doc.des }}
|
||||||
|
@ -88,9 +88,9 @@
|
||||||
{% elseif item.bank %}
|
{% elseif item.bank %}
|
||||||
{{item.bank.name}}
|
{{item.bank.name}}
|
||||||
{% elseif item.cashdesk %}
|
{% elseif item.cashdesk %}
|
||||||
{{item.salary.name}}
|
|
||||||
{% elseif item.salary %}
|
|
||||||
{{item.cashdesk.name}}
|
{{item.cashdesk.name}}
|
||||||
|
{% elseif item.salary %}
|
||||||
|
{{item.salary.name}}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{item.ref.name}}
|
{{item.ref.name}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
|
"@types/lodash": "^4.17.16",
|
||||||
"@types/node": "^22.14.1",
|
"@types/node": "^22.14.1",
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||||
|
|
|
@ -26,108 +26,107 @@
|
||||||
<v-progress-linear v-if="isLoading" indeterminate color="primary" />
|
<v-progress-linear v-if="isLoading" indeterminate color="primary" />
|
||||||
<v-card-text v-if="accountData.length > 0" class="pa-0">
|
<v-card-text v-if="accountData.length > 0" class="pa-0">
|
||||||
<div class="tree-container">
|
<div class="tree-container">
|
||||||
<div
|
<tree-node
|
||||||
v-for="item in accountData"
|
v-for="node in accountData"
|
||||||
:key="item.id"
|
:key="node.id"
|
||||||
class="tree-node"
|
:node="node"
|
||||||
:class="{ 'has-children': item.children && item.children.length > 0 }"
|
:selected-id="selectedAccount?.id"
|
||||||
>
|
@select="handleNodeSelect"
|
||||||
<div class="tree-node-content" @click="handleNodeClick(item)">
|
@toggle="toggleNode"
|
||||||
<div class="tree-node-toggle" @click.stop="toggleNode(item)">
|
/>
|
||||||
<v-icon v-if="item.children && item.children.length > 0">
|
|
||||||
{{ item.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}
|
|
||||||
</v-icon>
|
|
||||||
</div>
|
|
||||||
<div class="tree-node-icon">
|
|
||||||
<v-icon v-if="item.children && item.children.length > 0">mdi-folder</v-icon>
|
|
||||||
<v-icon v-else>mdi-file-document</v-icon>
|
|
||||||
</div>
|
|
||||||
<div class="tree-node-label" :class="{ 'selected': selectedAccount?.id === item.id }">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.isOpen && item.children && item.children.length > 0" class="tree-children">
|
|
||||||
<div
|
|
||||||
v-for="child in item.children"
|
|
||||||
:key="child.id"
|
|
||||||
class="tree-node"
|
|
||||||
:class="{ 'has-children': child.children && child.children.length > 0 }"
|
|
||||||
>
|
|
||||||
<div class="tree-node-content" @click="handleNodeClick(child)">
|
|
||||||
<div class="tree-node-toggle" @click.stop="toggleNode(child)">
|
|
||||||
<v-icon v-if="child.children && child.children.length > 0">
|
|
||||||
{{ child.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}
|
|
||||||
</v-icon>
|
|
||||||
</div>
|
|
||||||
<div class="tree-node-icon">
|
|
||||||
<v-icon v-if="child.children && child.children.length > 0">mdi-folder</v-icon>
|
|
||||||
<v-icon v-else>mdi-file-document</v-icon>
|
|
||||||
</div>
|
|
||||||
<div class="tree-node-label" :class="{ 'selected': selectedAccount?.id === child.id }">
|
|
||||||
{{ child.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="child.isOpen && child.children && child.children.length > 0" class="tree-children">
|
|
||||||
<div
|
|
||||||
v-for="grandChild in child.children"
|
|
||||||
:key="grandChild.id"
|
|
||||||
class="tree-node"
|
|
||||||
>
|
|
||||||
<div class="tree-node-content" @click="handleNodeClick(grandChild)">
|
|
||||||
<div class="tree-node-icon">
|
|
||||||
<v-icon>mdi-file-document</v-icon>
|
|
||||||
</div>
|
|
||||||
<div class="tree-node-label" :class="{ 'selected': selectedAccount?.id === grandChild.id }">
|
|
||||||
{{ grandChild.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text v-else>
|
<v-card-text v-else>
|
||||||
در حال بارگذاری...
|
{{ isLoading ? 'در حال بارگذاری...' : 'هیچ حسابی یافت نشد' }}
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
color="white"
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed, watch } from 'vue';
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { debounce } from 'lodash'; // برای دیبانس کردن loadChildren
|
import TreeNode from '@/components/forms/TreeNode.vue';
|
||||||
|
|
||||||
|
// اضافه کردن کش سراسری
|
||||||
|
const globalCache = {
|
||||||
|
data: null as AccountNode[] | null,
|
||||||
|
isLoading: false,
|
||||||
|
promise: null as Promise<void> | null,
|
||||||
|
};
|
||||||
|
|
||||||
|
type ValidationRule = (value: any) => boolean | string;
|
||||||
|
|
||||||
|
interface AccountNode {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
children?: AccountNode[];
|
||||||
|
isOpen?: boolean;
|
||||||
|
tableType?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'حساب'
|
default: 'حساب',
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
type: Array,
|
type: Array as () => ValidationRule[],
|
||||||
default: () => []
|
default: () => [],
|
||||||
}
|
},
|
||||||
|
initialAccount: {
|
||||||
|
type: Object as () => AccountNode | null,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'select']);
|
const emit = defineEmits(['update:modelValue', 'select', 'tableType', 'accountSelected']);
|
||||||
|
|
||||||
const menu = ref(false);
|
const menu = ref(false);
|
||||||
const accountData = ref([]);
|
const accountData = ref<AccountNode[]>([]);
|
||||||
const selectedAccount = ref(null);
|
const selectedAccount = ref<AccountNode | null>(null);
|
||||||
const cache = ref(new Map());
|
const cache = ref(new Map<string, AccountNode[]>());
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
const selectedAccountName = computed(() => {
|
const selectedAccountName = computed(() => {
|
||||||
return selectedAccount.value?.name || '';
|
return selectedAccount.value?.name || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// تابع برای رمزگشایی کاراکترهای یونیکد
|
// نمایش پیام خطا یا موفقیت
|
||||||
|
const showMessage = (text: string, color = 'success') => {
|
||||||
|
snackbar.value.text = text;
|
||||||
|
snackbar.value.color = color;
|
||||||
|
snackbar.value.show = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// رمزگشایی کاراکترهای یونیکد
|
||||||
const decodeUnicode = (str: string): string => {
|
const decodeUnicode = (str: string): string => {
|
||||||
try {
|
try {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
|
@ -141,9 +140,9 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// پردازش دادهها برای رمزگشایی نامها
|
// پردازش دادهها
|
||||||
const processTreeData = (items: any[]): any[] => {
|
const processTreeData = (items: any[]): any[] => {
|
||||||
return items.map(item => {
|
return items.map((item) => {
|
||||||
if (cache.value.has(`processed-${item.id}`)) {
|
if (cache.value.has(`processed-${item.id}`)) {
|
||||||
return cache.value.get(`processed-${item.id}`);
|
return cache.value.get(`processed-${item.id}`);
|
||||||
}
|
}
|
||||||
|
@ -151,88 +150,169 @@
|
||||||
...item,
|
...item,
|
||||||
name: decodeUnicode(item.name),
|
name: decodeUnicode(item.name),
|
||||||
children: item.children ? processTreeData(item.children) : [],
|
children: item.children ? processTreeData(item.children) : [],
|
||||||
isOpen: false
|
isOpen: false,
|
||||||
|
tableType: item.type,
|
||||||
};
|
};
|
||||||
cache.value.set(`processed-${item.id}`, processedItem);
|
cache.value.set(`processed-${item.id}`, processedItem);
|
||||||
return processedItem;
|
return processedItem;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// بارگذاری تنبل زیرشاخهها با دیبانس
|
// بارگذاری تمام دادهها
|
||||||
const loadChildren = debounce(async (node: any) => {
|
const fetchAllHesabdariTables = async () => {
|
||||||
if (cache.value.has(node.id)) {
|
// اگر دادهها در کش سراسری وجود دارند
|
||||||
node.children = cache.value.get(node.id);
|
if (globalCache.data) {
|
||||||
|
accountData.value = globalCache.data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// اگر در حال بارگذاری است، منتظر بمان
|
||||||
|
if (globalCache.isLoading && globalCache.promise) {
|
||||||
|
await globalCache.promise;
|
||||||
|
accountData.value = globalCache.data || [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// شروع بارگذاری جدید
|
||||||
|
globalCache.isLoading = true;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/hesabdari/tables/${node.id}/children`);
|
globalCache.promise = new Promise(async (resolve) => {
|
||||||
if (response.data.Success) {
|
try {
|
||||||
const children = processTreeData(response.data.data || []);
|
const response = await axios.get('/api/hesabdari/tables/all');
|
||||||
node.children = children;
|
if (response.data.Success && response.data.data) {
|
||||||
cache.value.set(node.id, children);
|
const allNodes = processTreeData(response.data.data.children || []);
|
||||||
|
globalCache.data = allNodes;
|
||||||
|
accountData.value = allNodes;
|
||||||
|
} else {
|
||||||
|
showMessage('هیچ حسابی یافت نشد', 'warning');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`خطا در بارگذاری زیرشاخههای گره ${node.id}:`, error);
|
console.error('خطا در بارگذاری حسابها:', error);
|
||||||
|
showMessage('خطا در بارگذاری حسابها', 'error');
|
||||||
|
} finally {
|
||||||
|
globalCache.isLoading = false;
|
||||||
|
isLoading.value = false;
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
}, 300);
|
});
|
||||||
|
|
||||||
const toggleNode = (node: any) => {
|
await globalCache.promise;
|
||||||
if (node.children && node.children.length > 0) {
|
} catch (error) {
|
||||||
node.isOpen = !node.isOpen;
|
console.error('خطا در بارگذاری حسابها:', error);
|
||||||
if (node.isOpen && (!node.children || node.children.length === 0)) {
|
showMessage('خطا در بارگذاری حسابها', 'error');
|
||||||
loadChildren(node);
|
globalCache.isLoading = false;
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// جستجوی حساب در دادههای موجود
|
||||||
|
const findAccount = (nodes: any[]): any => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.id === props.modelValue) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (node.children && node.children.length) {
|
||||||
|
const found = findAccount(node.children);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// تنظیم مقدار اولیه حساب
|
||||||
|
const initializeAccount = async () => {
|
||||||
|
if (props.modelValue) {
|
||||||
|
// اگر initialAccount وجود دارد و هنوز حساب انتخاب نشده
|
||||||
|
if (props.initialAccount && !selectedAccount.value) {
|
||||||
|
selectedAccount.value = {
|
||||||
|
id: props.modelValue,
|
||||||
|
name: props.initialAccount.name || '',
|
||||||
|
tableType: props.initialAccount.tableType || '',
|
||||||
|
};
|
||||||
|
emit('select', selectedAccount.value);
|
||||||
|
emit('accountSelected', selectedAccount.value);
|
||||||
|
if (selectedAccount.value.tableType) {
|
||||||
|
emit('tableType', selectedAccount.value.tableType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// لود دادهها اگر هنوز لود نشدهاند
|
||||||
|
if (!accountData.value.length) {
|
||||||
|
await fetchAllHesabdariTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
// جستجوی حساب در دادهها
|
||||||
|
const account = findAccount(accountData.value);
|
||||||
|
if (account && (!selectedAccount.value || selectedAccount.value.id !== account.id)) {
|
||||||
|
selectedAccount.value = account;
|
||||||
|
emit('select', account);
|
||||||
|
emit('accountSelected', account);
|
||||||
|
if (account.tableType && account.tableType !== selectedAccount.value?.tableType) {
|
||||||
|
emit('tableType', account.tableType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedAccount.value) {
|
||||||
|
selectedAccount.value = null;
|
||||||
|
emit('select', null);
|
||||||
|
emit('accountSelected', null);
|
||||||
|
emit('tableType', null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// مدیریت انتخاب آیتمها
|
// تغییر وضعیت گره
|
||||||
const handleNodeClick = (node: any) => {
|
const toggleNode = (node: any) => {
|
||||||
|
node.isOpen = !node.isOpen;
|
||||||
|
};
|
||||||
|
|
||||||
|
// مدیریت انتخاب گره
|
||||||
|
const handleNodeSelect = (node: any) => {
|
||||||
selectedAccount.value = node;
|
selectedAccount.value = node;
|
||||||
emit('update:modelValue', node.id);
|
emit('update:modelValue', node.id);
|
||||||
emit('select', node);
|
emit('select', node);
|
||||||
menu.value = false;
|
emit('accountSelected', node);
|
||||||
|
if (node.tableType) {
|
||||||
|
emit('tableType', node.tableType);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// باز کردن منو
|
// باز کردن منو
|
||||||
const openMenu = () => {
|
const openMenu = () => {
|
||||||
menu.value = true;
|
menu.value = true;
|
||||||
if (!accountData.value.length && !isLoading.value) {
|
if (!accountData.value.length && !isLoading.value) {
|
||||||
fetchHesabdariTables();
|
fetchAllHesabdariTables();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// بارگذاری اولیه گرههای ریشه
|
// اصلاح watch برای modelValue
|
||||||
const fetchHesabdariTables = async () => {
|
|
||||||
if (cache.value.has('root')) {
|
|
||||||
accountData.value = cache.value.get('root');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isLoading.value = true;
|
|
||||||
try {
|
|
||||||
const response = await axios.get('/api/hesabdari/tables');
|
|
||||||
if (response.data.Success && response.data.data) {
|
|
||||||
accountData.value = processTreeData(response.data.data[0].children || []);
|
|
||||||
cache.value.set('root', accountData.value);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('خطا در بارگذاری حسابها:', error);
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// دیباگ تعداد مونتها
|
|
||||||
onMounted(() => {
|
|
||||||
fetchHesabdariTables();
|
|
||||||
});
|
|
||||||
|
|
||||||
// بررسی تغییرات در vue-router
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
() => {
|
async (newVal, oldVal) => {
|
||||||
console.log('modelValue تغییر کرد، احتمالاً به دلیل ناوبری');
|
if (newVal === oldVal || newVal === selectedAccount.value?.id) return;
|
||||||
}
|
await initializeAccount();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// اضافه کردن watch برای initialAccount
|
||||||
|
watch(
|
||||||
|
() => props.initialAccount,
|
||||||
|
async (newVal) => {
|
||||||
|
if (newVal && props.modelValue) {
|
||||||
|
await initializeAccount();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.modelValue || props.initialAccount) {
|
||||||
|
initializeAccount();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -241,63 +321,4 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node {
|
|
||||||
margin-left: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-content:hover {
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-toggle {
|
|
||||||
width: 24px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-icon {
|
|
||||||
width: 24px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-label {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-family: 'Vazir', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-label.selected {
|
|
||||||
color: rgb(var(--v-theme-primary));
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-children {
|
|
||||||
margin-left: 24px;
|
|
||||||
border-right: 2px solid rgba(var(--v-theme-primary), 0.1);
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.v-menu__content) {
|
|
||||||
position: fixed !important;
|
|
||||||
z-index: 9999 !important;
|
|
||||||
transform-origin: center top !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.v-overlay__content) {
|
|
||||||
position: fixed !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
448
webUI/src/components/forms/Hbankaccountsearch.vue
Normal file
448
webUI/src/components/forms/Hbankaccountsearch.vue
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-text-field
|
||||||
|
v-bind="props"
|
||||||
|
v-model="displayValue"
|
||||||
|
variant="outlined"
|
||||||
|
:error-messages="errorMessages"
|
||||||
|
:rules="combinedRules"
|
||||||
|
:label="label || 'جستجوی حساب بانکی'"
|
||||||
|
class="my-0"
|
||||||
|
prepend-inner-icon="mdi-bank"
|
||||||
|
clearable
|
||||||
|
@click:clear="clearSelection"
|
||||||
|
:loading="loading"
|
||||||
|
@keydown.enter="handleEnter"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
>
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card min-width="300" max-width="400" class="search-card">
|
||||||
|
<v-card-text class="pa-2">
|
||||||
|
<template v-if="!loading">
|
||||||
|
<v-list density="compact" class="list-container">
|
||||||
|
<template v-if="filteredItems.length > 0">
|
||||||
|
<v-list-item
|
||||||
|
v-for="item in filteredItems"
|
||||||
|
:key="item.id"
|
||||||
|
@click="selectItem(item)"
|
||||||
|
class="mb-1"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-right">{{ item.name }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-right">{{ item.accountNum }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title class="text-center text-grey">
|
||||||
|
نتیجهای یافت نشد
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-list>
|
||||||
|
<v-btn
|
||||||
|
v-if="filteredItems.length === 0"
|
||||||
|
block
|
||||||
|
color="primary"
|
||||||
|
class="mt-2"
|
||||||
|
@click="showAddDialog = true"
|
||||||
|
>
|
||||||
|
افزودن حساب بانکی جدید
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-progress-circular
|
||||||
|
v-else
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
class="d-flex mx-auto my-4"
|
||||||
|
></v-progress-circular>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
|
<v-dialog v-model="showAddDialog" :fullscreen="$vuetify.display.mobile" max-width="600">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="primary" density="compact" class="sticky-toolbar">
|
||||||
|
<v-toolbar-title>افزودن حساب بانکی جدید</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip text="بستن">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
v-bind="props"
|
||||||
|
@click="showAddDialog = false"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ذخیره">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-content-save"
|
||||||
|
v-bind="props"
|
||||||
|
@click="saveBankAccount"
|
||||||
|
:loading="saving"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-card-text class="content-container">
|
||||||
|
<v-form @submit.prevent="saveBankAccount">
|
||||||
|
<v-row class="mt-4">
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.bank"
|
||||||
|
label="نام بانک *"
|
||||||
|
required
|
||||||
|
:error-messages="bankErrors"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.owner"
|
||||||
|
label="صاحب حساب"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.accountNum"
|
||||||
|
label="شماره حساب"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.cardNum"
|
||||||
|
label="شماره کارت"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.shabaNum"
|
||||||
|
label="شماره شبا"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.shobe"
|
||||||
|
label="شعبه"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.posNum"
|
||||||
|
label="شماره دستگاه کارتخوان"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAccount.mobileInternetBank"
|
||||||
|
label="شماره موبایل اینترنت بانک"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-textarea
|
||||||
|
v-model="newAccount.des"
|
||||||
|
label="توضیحات"
|
||||||
|
rows="3"
|
||||||
|
></v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
color="white"
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Hbankaccountsearch',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [Object, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: 'حساب بانکی'
|
||||||
|
},
|
||||||
|
returnObject: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedItem: null,
|
||||||
|
items: [],
|
||||||
|
loading: false,
|
||||||
|
menu: false,
|
||||||
|
searchQuery: '',
|
||||||
|
totalItems: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
searchTimeout: null,
|
||||||
|
showAddDialog: false,
|
||||||
|
saving: false,
|
||||||
|
snackbar: {
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success'
|
||||||
|
},
|
||||||
|
errorMessages: [],
|
||||||
|
newAccount: {
|
||||||
|
bank: '',
|
||||||
|
accountNum: '',
|
||||||
|
cardNum: '',
|
||||||
|
shabaNum: '',
|
||||||
|
des: '',
|
||||||
|
owner: '',
|
||||||
|
shobe: '',
|
||||||
|
posNum: '',
|
||||||
|
mobileInternetBank: '',
|
||||||
|
code: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredItems() {
|
||||||
|
return Array.isArray(this.items) ? this.items : [];
|
||||||
|
},
|
||||||
|
displayValue: {
|
||||||
|
get() {
|
||||||
|
if (this.menu) {
|
||||||
|
return this.searchQuery;
|
||||||
|
}
|
||||||
|
return this.selectedItem ? this.selectedItem.name : this.searchQuery;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.searchQuery = value;
|
||||||
|
if (!value) {
|
||||||
|
this.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bankErrors() {
|
||||||
|
if (!this.newAccount.bank) return ['نام بانک الزامی است'];
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
combinedRules() {
|
||||||
|
return [
|
||||||
|
v => !!v || 'انتخاب حساب بانکی الزامی است',
|
||||||
|
...this.rules
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
if (this.returnObject) {
|
||||||
|
this.selectedItem = newVal;
|
||||||
|
this.searchQuery = newVal.name;
|
||||||
|
} else {
|
||||||
|
this.fetchAccountById(newVal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.searchQuery = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
searchQuery: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.currentPage = 1;
|
||||||
|
if (this.searchTimeout) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
}
|
||||||
|
this.searchTimeout = setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showAddDialog: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.newAccount.bank = this.searchQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showMessage(text, color = 'success') {
|
||||||
|
this.snackbar.text = text;
|
||||||
|
this.snackbar.color = color;
|
||||||
|
this.snackbar.show = true;
|
||||||
|
},
|
||||||
|
async fetchData() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/bank/search', {
|
||||||
|
page: this.currentPage,
|
||||||
|
itemsPerPage: this.itemsPerPage,
|
||||||
|
search: this.searchQuery
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.items) {
|
||||||
|
this.items = response.data.items;
|
||||||
|
this.totalItems = response.data.total;
|
||||||
|
} else {
|
||||||
|
this.items = [];
|
||||||
|
this.totalItems = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modelValue) {
|
||||||
|
if (this.returnObject) {
|
||||||
|
this.selectedItem = this.modelValue;
|
||||||
|
} else {
|
||||||
|
this.selectedItem = this.items.find(item => item.id === this.modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری دادهها:', error);
|
||||||
|
this.showMessage('خطا در بارگذاری دادهها', 'error');
|
||||||
|
this.items = [];
|
||||||
|
this.totalItems = 0;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveBankAccount() {
|
||||||
|
if (!this.newAccount.bank) {
|
||||||
|
this.showMessage('نام بانک الزامی است', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/bank/mod/' + (this.newAccount.code || 0), {
|
||||||
|
name: this.newAccount.bank,
|
||||||
|
des: this.newAccount.des,
|
||||||
|
owner: this.newAccount.owner,
|
||||||
|
accountNum: this.newAccount.accountNum,
|
||||||
|
cardNum: this.newAccount.cardNum,
|
||||||
|
shaba: this.newAccount.shabaNum,
|
||||||
|
shobe: this.newAccount.shobe,
|
||||||
|
posNum: this.newAccount.posNum,
|
||||||
|
mobileInternetbank: this.newAccount.mobileInternetBank
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.result === 1) {
|
||||||
|
this.showMessage('حساب بانکی با موفقیت ثبت شد');
|
||||||
|
this.showAddDialog = false;
|
||||||
|
this.fetchData();
|
||||||
|
} else if (response.data.result === 2) {
|
||||||
|
this.showMessage('این حساب بانکی قبلاً ثبت شده است', 'error');
|
||||||
|
} else if (response.data.result === 3) {
|
||||||
|
this.showMessage('نام حساب بانکی نمیتواند خالی باشد', 'error');
|
||||||
|
} else {
|
||||||
|
this.showMessage('خطا در ثبت حساب بانکی', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در ثبت حساب بانکی:', error);
|
||||||
|
this.showMessage('خطا در ثبت حساب بانکی', 'error');
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectItem(item) {
|
||||||
|
this.selectedItem = item;
|
||||||
|
this.searchQuery = item.name;
|
||||||
|
this.$emit('update:modelValue', this.returnObject ? item : item.id);
|
||||||
|
this.menu = false;
|
||||||
|
this.errorMessages = [];
|
||||||
|
},
|
||||||
|
clearSelection() {
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.searchQuery = '';
|
||||||
|
this.$emit('update:modelValue', null);
|
||||||
|
this.errorMessages = ['انتخاب حساب بانکی الزامی است'];
|
||||||
|
},
|
||||||
|
handleEnter() {
|
||||||
|
if (!this.loading && this.filteredItems.length === 0) {
|
||||||
|
this.showAddDialog = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchAccountById(id) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/componenets/bank/get/${id}`);
|
||||||
|
if (response.data && response.data.item) {
|
||||||
|
this.selectedItem = response.data.item;
|
||||||
|
this.searchQuery = response.data.item.name;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در دریافت حساب بانکی:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-toolbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-menu__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
transform-origin: center top !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-overlay__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.content-container {
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
392
webUI/src/components/forms/Hcashdesksearch.vue
Normal file
392
webUI/src/components/forms/Hcashdesksearch.vue
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-text-field
|
||||||
|
v-bind="props"
|
||||||
|
v-model="displayValue"
|
||||||
|
variant="outlined"
|
||||||
|
:error-messages="errorMessages"
|
||||||
|
:rules="combinedRules"
|
||||||
|
:label="label || 'جستجوی صندوق'"
|
||||||
|
class="my-0"
|
||||||
|
prepend-inner-icon="mdi-cash-register"
|
||||||
|
clearable
|
||||||
|
@click:clear="clearSelection"
|
||||||
|
:loading="loading"
|
||||||
|
@keydown.enter="handleEnter"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
>
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card min-width="300" max-width="400" class="search-card">
|
||||||
|
<v-card-text class="pa-2">
|
||||||
|
<template v-if="!loading">
|
||||||
|
<v-list density="compact" class="list-container">
|
||||||
|
<template v-if="filteredItems.length > 0">
|
||||||
|
<v-list-item
|
||||||
|
v-for="item in filteredItems"
|
||||||
|
:key="item.id"
|
||||||
|
@click="selectItem(item)"
|
||||||
|
class="mb-1"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-right">{{ item.name }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-right">{{ item.balance }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title class="text-center text-grey">
|
||||||
|
نتیجهای یافت نشد
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-list>
|
||||||
|
<v-btn
|
||||||
|
v-if="filteredItems.length === 0"
|
||||||
|
block
|
||||||
|
color="primary"
|
||||||
|
class="mt-2"
|
||||||
|
@click="showAddDialog = true"
|
||||||
|
>
|
||||||
|
افزودن صندوق جدید
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-progress-circular
|
||||||
|
v-else
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
class="d-flex mx-auto my-4"
|
||||||
|
></v-progress-circular>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
|
<v-dialog v-model="showAddDialog" :fullscreen="$vuetify.display.mobile" max-width="600">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="primary" density="compact" class="sticky-toolbar">
|
||||||
|
<v-toolbar-title>افزودن صندوق جدید</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip text="بستن">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
v-bind="props"
|
||||||
|
@click="showAddDialog = false"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ذخیره">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-content-save"
|
||||||
|
v-bind="props"
|
||||||
|
@click="saveCashdesk"
|
||||||
|
:loading="saving"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-card-text class="content-container">
|
||||||
|
<v-form @submit.prevent="saveCashdesk">
|
||||||
|
<v-row class="mt-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newCashdesk.name"
|
||||||
|
label="نام صندوق *"
|
||||||
|
required
|
||||||
|
:error-messages="nameErrors"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-textarea
|
||||||
|
v-model="newCashdesk.des"
|
||||||
|
label="توضیحات"
|
||||||
|
rows="3"
|
||||||
|
></v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
color="white"
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Hcashdesksearch',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [Object, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: 'صندوق'
|
||||||
|
},
|
||||||
|
returnObject: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedItem: null,
|
||||||
|
items: [],
|
||||||
|
loading: false,
|
||||||
|
menu: false,
|
||||||
|
searchQuery: '',
|
||||||
|
totalItems: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
searchTimeout: null,
|
||||||
|
showAddDialog: false,
|
||||||
|
saving: false,
|
||||||
|
snackbar: {
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success'
|
||||||
|
},
|
||||||
|
errorMessages: [],
|
||||||
|
newCashdesk: {
|
||||||
|
name: '',
|
||||||
|
des: '',
|
||||||
|
code: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredItems() {
|
||||||
|
return Array.isArray(this.items) ? this.items : [];
|
||||||
|
},
|
||||||
|
displayValue: {
|
||||||
|
get() {
|
||||||
|
if (this.menu) {
|
||||||
|
return this.searchQuery;
|
||||||
|
}
|
||||||
|
return this.selectedItem ? this.selectedItem.name : this.searchQuery;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.searchQuery = value;
|
||||||
|
if (!value) {
|
||||||
|
this.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameErrors() {
|
||||||
|
if (!this.newCashdesk.name) return ['نام صندوق الزامی است'];
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
combinedRules() {
|
||||||
|
return [
|
||||||
|
v => !!v || 'انتخاب صندوق الزامی است',
|
||||||
|
...this.rules
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
if (this.returnObject) {
|
||||||
|
this.selectedItem = newVal;
|
||||||
|
this.searchQuery = newVal.name;
|
||||||
|
} else {
|
||||||
|
this.fetchCashDeskById(newVal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.searchQuery = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
searchQuery: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.currentPage = 1;
|
||||||
|
if (this.searchTimeout) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
}
|
||||||
|
this.searchTimeout = setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showAddDialog: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.newCashdesk.name = this.searchQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showMessage(text, color = 'success') {
|
||||||
|
this.snackbar.text = text;
|
||||||
|
this.snackbar.color = color;
|
||||||
|
this.snackbar.show = true;
|
||||||
|
},
|
||||||
|
async fetchData() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/cashdesk/search', {
|
||||||
|
page: this.currentPage,
|
||||||
|
itemsPerPage: this.itemsPerPage,
|
||||||
|
search: this.searchQuery
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.items) {
|
||||||
|
this.items = response.data.items;
|
||||||
|
this.totalItems = response.data.total;
|
||||||
|
} else {
|
||||||
|
this.items = [];
|
||||||
|
this.totalItems = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modelValue) {
|
||||||
|
if (this.returnObject) {
|
||||||
|
this.selectedItem = this.modelValue;
|
||||||
|
} else {
|
||||||
|
this.selectedItem = this.items.find(item => item.id === this.modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری دادهها:', error);
|
||||||
|
this.showMessage('خطا در بارگذاری دادهها', 'error');
|
||||||
|
this.items = [];
|
||||||
|
this.totalItems = 0;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveCashdesk() {
|
||||||
|
if (!this.newCashdesk.name) {
|
||||||
|
this.showMessage('نام صندوق الزامی است', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/cashdesk/mod/' + (this.newCashdesk.code || 0), {
|
||||||
|
name: this.newCashdesk.name,
|
||||||
|
des: this.newCashdesk.des
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.result === 1) {
|
||||||
|
this.showMessage('صندوق با موفقیت ثبت شد');
|
||||||
|
this.showAddDialog = false;
|
||||||
|
this.fetchData();
|
||||||
|
} else if (response.data.result === 2) {
|
||||||
|
this.showMessage('این صندوق قبلاً ثبت شده است', 'error');
|
||||||
|
} else if (response.data.result === 3) {
|
||||||
|
this.showMessage('نام صندوق نمیتواند خالی باشد', 'error');
|
||||||
|
} else {
|
||||||
|
this.showMessage('خطا در ثبت صندوق', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در ثبت صندوق:', error);
|
||||||
|
this.showMessage('خطا در ثبت صندوق', 'error');
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectItem(item) {
|
||||||
|
this.selectedItem = item;
|
||||||
|
this.searchQuery = item.name;
|
||||||
|
this.$emit('update:modelValue', this.returnObject ? item : item.id);
|
||||||
|
this.menu = false;
|
||||||
|
this.errorMessages = [];
|
||||||
|
},
|
||||||
|
clearSelection() {
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.searchQuery = '';
|
||||||
|
this.$emit('update:modelValue', null);
|
||||||
|
this.errorMessages = ['انتخاب صندوق الزامی است'];
|
||||||
|
},
|
||||||
|
handleEnter() {
|
||||||
|
if (!this.loading && this.filteredItems.length === 0) {
|
||||||
|
this.showAddDialog = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchCashDeskById(id) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/componenets/cashdesk/get/${id}`);
|
||||||
|
if (response.data && response.data.item) {
|
||||||
|
this.selectedItem = response.data.item;
|
||||||
|
this.searchQuery = response.data.item.name;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در دریافت صندوق:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-toolbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-menu__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
transform-origin: center top !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-overlay__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.content-container {
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,13 +9,15 @@
|
||||||
:error-messages="errorMessages"
|
:error-messages="errorMessages"
|
||||||
:rules="combinedRules"
|
:rules="combinedRules"
|
||||||
:label="label"
|
:label="label"
|
||||||
class=""
|
class="my-0"
|
||||||
prepend-inner-icon="mdi-package-variant"
|
prepend-inner-icon="mdi-package-variant"
|
||||||
clearable
|
clearable
|
||||||
@click:clear="clearSelection"
|
@click:clear="clearSelection"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@keydown.enter="handleEnter"
|
@keydown.enter="handleEnter"
|
||||||
hide-details="auto"
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
>
|
>
|
||||||
<template v-slot:append-inner>
|
<template v-slot:append-inner>
|
||||||
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
@ -23,7 +25,7 @@
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card min-width="300" max-width="400">
|
<v-card min-width="300" max-width="400" class="search-card">
|
||||||
<v-card-text class="pa-2">
|
<v-card-text class="pa-2">
|
||||||
<template v-if="!loading">
|
<template v-if="!loading">
|
||||||
<v-list density="compact" class="list-container">
|
<v-list density="compact" class="list-container">
|
||||||
|
@ -68,46 +70,12 @@
|
||||||
|
|
||||||
<v-dialog v-model="showAddDialog" :fullscreen="$vuetify.display.mobile" max-width="800">
|
<v-dialog v-model="showAddDialog" :fullscreen="$vuetify.display.mobile" max-width="800">
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-toolbar color="primary" density="compact" class="sticky-toolbar">
|
<v-card-title class="text-h5">
|
||||||
<v-toolbar-title>افزودن کالا/خدمت جدید</v-toolbar-title>
|
افزودن کالا/خدمت جدید
|
||||||
<v-spacer></v-spacer>
|
</v-card-title>
|
||||||
<v-tooltip text="بستن">
|
<v-card-text>
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn
|
|
||||||
icon="mdi-close"
|
|
||||||
v-bind="props"
|
|
||||||
@click="showAddDialog = false"
|
|
||||||
></v-btn>
|
|
||||||
</template>
|
|
||||||
</v-tooltip>
|
|
||||||
<v-tooltip text="ذخیره">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn
|
|
||||||
icon="mdi-content-save"
|
|
||||||
v-bind="props"
|
|
||||||
@click="saveCommodity"
|
|
||||||
:loading="saving"
|
|
||||||
></v-btn>
|
|
||||||
</template>
|
|
||||||
</v-tooltip>
|
|
||||||
</v-toolbar>
|
|
||||||
|
|
||||||
<v-tabs
|
|
||||||
v-model="tabs"
|
|
||||||
color="primary"
|
|
||||||
show-arrows
|
|
||||||
class="sticky-tabs"
|
|
||||||
>
|
|
||||||
<v-tab value="basic" class="flex-grow-1">اطلاعات پایه</v-tab>
|
|
||||||
<v-tab value="details" class="flex-grow-1">جزئیات</v-tab>
|
|
||||||
<v-tab value="pricing" class="flex-grow-1">قیمتگذاری</v-tab>
|
|
||||||
</v-tabs>
|
|
||||||
|
|
||||||
<v-card-text class="content-container">
|
|
||||||
<v-window v-model="tabs">
|
|
||||||
<v-window-item value="basic">
|
|
||||||
<v-form @submit.prevent="saveCommodity">
|
<v-form @submit.prevent="saveCommodity">
|
||||||
<v-row class="mt-4">
|
<v-row>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newCommodity.name"
|
v-model="newCommodity.name"
|
||||||
|
@ -148,11 +116,7 @@
|
||||||
></v-textarea>
|
></v-textarea>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
<v-row>
|
||||||
</v-window-item>
|
|
||||||
|
|
||||||
<v-window-item value="details">
|
|
||||||
<v-row class="mt-4">
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newCommodity.brand"
|
v-model="newCommodity.brand"
|
||||||
|
@ -185,10 +149,7 @@
|
||||||
></v-switch>
|
></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-window-item>
|
<v-row>
|
||||||
|
|
||||||
<v-window-item value="pricing">
|
|
||||||
<v-row class="mt-4">
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newCommodity.basePrice"
|
v-model="newCommodity.basePrice"
|
||||||
|
@ -220,9 +181,17 @@
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-window-item>
|
</v-form>
|
||||||
</v-window>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="grey-darken-1" variant="text" @click="showAddDialog = false">
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" variant="text" @click="saveCommodity" :loading="saving">
|
||||||
|
ذخیره
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
@ -281,7 +250,6 @@ export default defineComponent({
|
||||||
itemsPerPage: 10,
|
itemsPerPage: 10,
|
||||||
searchTimeout: null,
|
searchTimeout: null,
|
||||||
showAddDialog: false,
|
showAddDialog: false,
|
||||||
tabs: 'basic',
|
|
||||||
saving: false,
|
saving: false,
|
||||||
commodityTypes: ['کالا', 'خدمت'],
|
commodityTypes: ['کالا', 'خدمت'],
|
||||||
units: ['عدد', 'کیلوگرم', 'گرم', 'متر', 'سانتیمتر', 'لیتر', 'متر مربع', 'متر مکعب'],
|
units: ['عدد', 'کیلوگرم', 'گرم', 'متر', 'سانتیمتر', 'لیتر', 'متر مربع', 'متر مکعب'],
|
||||||
|
@ -349,10 +317,14 @@ export default defineComponent({
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
if (this.returnObject) {
|
if (this.returnObject) {
|
||||||
this.selectedItem = newVal
|
this.selectedItem = newVal;
|
||||||
} else {
|
} else {
|
||||||
this.selectedItem = this.items.find(item => item.id === newVal)
|
this.selectedItem = this.items.find(item => item.id === newVal) || { id: newVal, name: 'در حال بارگذاری...' };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedItem = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
immediate: true
|
immediate: true
|
||||||
|
@ -408,6 +380,9 @@ export default defineComponent({
|
||||||
this.selectedItem = this.modelValue
|
this.selectedItem = this.modelValue
|
||||||
} else {
|
} else {
|
||||||
this.selectedItem = this.items.find(item => item.id === this.modelValue)
|
this.selectedItem = this.items.find(item => item.id === this.modelValue)
|
||||||
|
if (!this.selectedItem) {
|
||||||
|
await this.fetchSingleCommodity(this.modelValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -418,6 +393,26 @@ export default defineComponent({
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async fetchSingleCommodity(id) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/commodity/${id}`)
|
||||||
|
if (response.data && response.data.id) {
|
||||||
|
this.items.push(response.data)
|
||||||
|
this.selectedItem = response.data
|
||||||
|
this.searchQuery = response.data.name
|
||||||
|
this.$emit('update:modelValue', this.returnObject ? response.data : response.data.id)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showMessage('خطا در بارگذاری کالا', 'error')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setValue(commodity) {
|
||||||
|
if (commodity) {
|
||||||
|
this.selectedItem = commodity
|
||||||
|
this.searchQuery = commodity.name
|
||||||
|
this.$emit('update:modelValue', this.returnObject ? commodity : commodity.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
async saveCommodity() {
|
async saveCommodity() {
|
||||||
if (!this.newCommodity.name) {
|
if (!this.newCommodity.name) {
|
||||||
this.showMessage('نام کالا/خدمت الزامی است', 'error')
|
this.showMessage('نام کالا/خدمت الزامی است', 'error')
|
||||||
|
@ -485,43 +480,4 @@ export default defineComponent({
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-container {
|
|
||||||
max-height: 500px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-toolbar {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-tabs {
|
|
||||||
position: sticky;
|
|
||||||
top: 48px;
|
|
||||||
z-index: 1;
|
|
||||||
overflow-x: auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.v-menu__content) {
|
|
||||||
position: fixed !important;
|
|
||||||
z-index: 9999 !important;
|
|
||||||
transform-origin: center top !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.v-overlay__content) {
|
|
||||||
position: fixed !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.content-container {
|
|
||||||
max-height: calc(100vh - 120px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-tabs {
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -9,13 +9,15 @@
|
||||||
:error-messages="errorMessages"
|
:error-messages="errorMessages"
|
||||||
:rules="combinedRules"
|
:rules="combinedRules"
|
||||||
:label="label"
|
:label="label"
|
||||||
class=""
|
class="my-0"
|
||||||
prepend-inner-icon="mdi-account"
|
prepend-inner-icon="mdi-account"
|
||||||
clearable
|
clearable
|
||||||
@click:clear="clearSelection"
|
@click:clear="clearSelection"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@keydown.enter="handleEnter"
|
@keydown.enter="handleEnter"
|
||||||
hide-details="auto"
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
>
|
>
|
||||||
<template v-slot:append-inner>
|
<template v-slot:append-inner>
|
||||||
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
@ -23,7 +25,7 @@
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card min-width="300" max-width="400">
|
<v-card min-width="300" max-width="400" class="search-card">
|
||||||
<v-card-text class="pa-2">
|
<v-card-text class="pa-2">
|
||||||
<template v-if="!loading">
|
<template v-if="!loading">
|
||||||
<v-list density="compact" class="list-container">
|
<v-list density="compact" class="list-container">
|
||||||
|
|
373
webUI/src/components/forms/Hsalarysearch.vue
Normal file
373
webUI/src/components/forms/Hsalarysearch.vue
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-text-field
|
||||||
|
v-bind="props"
|
||||||
|
v-model="displayValue"
|
||||||
|
variant="outlined"
|
||||||
|
:error-messages="errorMessages"
|
||||||
|
:rules="combinedRules"
|
||||||
|
:label="label || 'جستجوی تنخواهگردان'"
|
||||||
|
class=""
|
||||||
|
prepend-inner-icon="mdi-cash"
|
||||||
|
clearable
|
||||||
|
@click:clear="clearSelection"
|
||||||
|
:loading="loading"
|
||||||
|
@keydown.enter="handleEnter"
|
||||||
|
hide-details="auto"
|
||||||
|
>
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card min-width="300" max-width="400">
|
||||||
|
<v-card-text class="pa-2">
|
||||||
|
<template v-if="!loading">
|
||||||
|
<v-list density="compact" class="list-container">
|
||||||
|
<template v-if="filteredItems.length > 0">
|
||||||
|
<v-list-item
|
||||||
|
v-for="item in filteredItems"
|
||||||
|
:key="item.id"
|
||||||
|
@click="selectItem(item)"
|
||||||
|
class="mb-1"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-right">{{ item.name }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-right">{{ item.balance }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title class="text-center text-grey">
|
||||||
|
نتیجهای یافت نشد
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-list>
|
||||||
|
<v-btn
|
||||||
|
v-if="filteredItems.length === 0"
|
||||||
|
block
|
||||||
|
color="primary"
|
||||||
|
class="mt-2"
|
||||||
|
@click="showAddDialog = true"
|
||||||
|
>
|
||||||
|
افزودن تنخواهگردان جدید
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-progress-circular
|
||||||
|
v-else
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
class="d-flex mx-auto my-4"
|
||||||
|
></v-progress-circular>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
|
<v-dialog v-model="showAddDialog" :fullscreen="$vuetify.display.mobile" max-width="600">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="primary" density="compact" class="sticky-toolbar">
|
||||||
|
<v-toolbar-title>افزودن تنخواهگردان جدید</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip text="بستن">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
v-bind="props"
|
||||||
|
@click="showAddDialog = false"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ذخیره">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-content-save"
|
||||||
|
v-bind="props"
|
||||||
|
@click="saveSalary"
|
||||||
|
:loading="saving"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-card-text class="content-container">
|
||||||
|
<v-form @submit.prevent="saveSalary">
|
||||||
|
<v-row class="mt-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newSalary.name"
|
||||||
|
label="نام تنخواهگردان *"
|
||||||
|
required
|
||||||
|
:error-messages="nameErrors"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-textarea
|
||||||
|
v-model="newSalary.des"
|
||||||
|
label="توضیحات"
|
||||||
|
rows="3"
|
||||||
|
></v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
color="white"
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Hsalarysearch',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [Object, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: 'تنخواهگردان'
|
||||||
|
},
|
||||||
|
returnObject: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedItem: null,
|
||||||
|
items: [],
|
||||||
|
loading: false,
|
||||||
|
menu: false,
|
||||||
|
searchQuery: '',
|
||||||
|
totalItems: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
searchTimeout: null,
|
||||||
|
showAddDialog: false,
|
||||||
|
saving: false,
|
||||||
|
snackbar: {
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success'
|
||||||
|
},
|
||||||
|
errorMessages: [],
|
||||||
|
newSalary: {
|
||||||
|
name: '',
|
||||||
|
des: '',
|
||||||
|
code: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredItems() {
|
||||||
|
return Array.isArray(this.items) ? this.items : [];
|
||||||
|
},
|
||||||
|
displayValue: {
|
||||||
|
get() {
|
||||||
|
if (this.menu) {
|
||||||
|
return this.searchQuery;
|
||||||
|
}
|
||||||
|
return this.selectedItem ? this.selectedItem.name : this.searchQuery;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.searchQuery = value;
|
||||||
|
if (!value) {
|
||||||
|
this.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameErrors() {
|
||||||
|
if (!this.newSalary.name) return ['نام تنخواهگردان الزامی است'];
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
combinedRules() {
|
||||||
|
return [
|
||||||
|
v => !!v || 'انتخاب تنخواهگردان الزامی است',
|
||||||
|
...this.rules
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (this.returnObject) {
|
||||||
|
this.selectedItem = newVal;
|
||||||
|
} else {
|
||||||
|
this.selectedItem = this.items.find(item => item.id === newVal);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
searchQuery: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.currentPage = 1;
|
||||||
|
if (this.searchTimeout) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
}
|
||||||
|
this.searchTimeout = setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showAddDialog: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.newSalary.name = this.searchQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showMessage(text, color = 'success') {
|
||||||
|
this.snackbar.text = text;
|
||||||
|
this.snackbar.color = color;
|
||||||
|
this.snackbar.show = true;
|
||||||
|
},
|
||||||
|
async fetchData() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/salary/search', {
|
||||||
|
page: this.currentPage,
|
||||||
|
itemsPerPage: this.itemsPerPage,
|
||||||
|
search: this.searchQuery
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.items) {
|
||||||
|
this.items = response.data.items;
|
||||||
|
this.totalItems = response.data.total;
|
||||||
|
} else {
|
||||||
|
this.items = [];
|
||||||
|
this.totalItems = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modelValue) {
|
||||||
|
if (this.returnObject) {
|
||||||
|
this.selectedItem = this.modelValue;
|
||||||
|
} else {
|
||||||
|
this.selectedItem = this.items.find(item => item.id === this.modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری دادهها:', error);
|
||||||
|
this.showMessage('خطا در بارگذاری دادهها', 'error');
|
||||||
|
this.items = [];
|
||||||
|
this.totalItems = 0;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveSalary() {
|
||||||
|
if (!this.newSalary.name) {
|
||||||
|
this.showMessage('نام تنخواهگردان الزامی است', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/salary/mod/' + (this.newSalary.code || 0), {
|
||||||
|
name: this.newSalary.name,
|
||||||
|
des: this.newSalary.des
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.result === 1) {
|
||||||
|
this.showMessage('تنخواهگردان با موفقیت ثبت شد');
|
||||||
|
this.showAddDialog = false;
|
||||||
|
this.fetchData();
|
||||||
|
} else if (response.data.result === 2) {
|
||||||
|
this.showMessage('این تنخواهگردان قبلاً ثبت شده است', 'error');
|
||||||
|
} else if (response.data.result === 3) {
|
||||||
|
this.showMessage('نام تنخواهگردان نمیتواند خالی باشد', 'error');
|
||||||
|
} else {
|
||||||
|
this.showMessage('خطا در ثبت تنخواهگردان', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در ثبت تنخواهگردان:', error);
|
||||||
|
this.showMessage('خطا در ثبت تنخواهگردان', 'error');
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectItem(item) {
|
||||||
|
this.selectedItem = item;
|
||||||
|
this.searchQuery = item.name;
|
||||||
|
this.$emit('update:modelValue', this.returnObject ? item : item.id);
|
||||||
|
this.menu = false;
|
||||||
|
this.errorMessages = [];
|
||||||
|
},
|
||||||
|
clearSelection() {
|
||||||
|
this.selectedItem = null;
|
||||||
|
this.searchQuery = '';
|
||||||
|
this.$emit('update:modelValue', null);
|
||||||
|
this.errorMessages = ['انتخاب تنخواهگردان الزامی است'];
|
||||||
|
},
|
||||||
|
handleEnter() {
|
||||||
|
if (!this.loading && this.filteredItems.length === 0) {
|
||||||
|
this.showAddDialog = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-toolbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-menu__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
transform-origin: center top !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-overlay__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.content-container {
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
104
webUI/src/components/forms/TreeNode.vue
Normal file
104
webUI/src/components/forms/TreeNode.vue
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<div class="tree-node" :class="{ 'has-children': node.children && node.children.length > 0 }">
|
||||||
|
<div class="tree-node-content" @click="handleNodeClick">
|
||||||
|
<div class="tree-node-toggle" @click.stop="toggleNode">
|
||||||
|
<v-icon v-if="node.children && node.children.length > 0">
|
||||||
|
{{ node.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-icon">
|
||||||
|
<v-icon v-if="node.children && node.children.length > 0">mdi-folder</v-icon>
|
||||||
|
<v-icon v-else>mdi-file-document</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-label" :class="{ 'selected': selectedId === node.id }">
|
||||||
|
{{ node.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="node.isOpen && node.children && node.children.length > 0" class="tree-children">
|
||||||
|
<tree-node
|
||||||
|
v-for="child in node.children"
|
||||||
|
:key="child.id"
|
||||||
|
:node="child"
|
||||||
|
:selected-id="selectedId"
|
||||||
|
@select="$emit('select', $event)"
|
||||||
|
@toggle="$emit('toggle', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineEmits } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedId: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['select', 'toggle']);
|
||||||
|
|
||||||
|
const handleNodeClick = () => {
|
||||||
|
emit('select', props.node);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleNode = () => {
|
||||||
|
emit('toggle', props.node);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-node {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-content:hover {
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-toggle {
|
||||||
|
width: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-icon {
|
||||||
|
width: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-family: 'Vazir', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-label.selected {
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-children {
|
||||||
|
margin-left: 24px;
|
||||||
|
border-right: 2px solid rgba(var(--v-theme-primary), 0.1);
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -277,6 +277,7 @@ const fa_lang = {
|
||||||
fetch_data_error: "خطا در گرفتن داده از {url}"
|
fetch_data_error: "خطا در گرفتن داده از {url}"
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
|
change_password: 'تغییر کلمه عبور',
|
||||||
download: 'دانلود',
|
download: 'دانلود',
|
||||||
delete_group: 'حذف گروهی',
|
delete_group: 'حذف گروهی',
|
||||||
add_new_transfer: 'سند انتقال جدید',
|
add_new_transfer: 'سند انتقال جدید',
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-toolbar
|
<v-toolbar color="toolbar" title="اسناد حسابداری">
|
||||||
color="toolbar"
|
|
||||||
title="اسناد حسابداری"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
|
@ -12,49 +9,144 @@
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip v-if="isPluginActive('accpro')" text="افزودن سند حسابداری" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-plus" variant="text" color="success" :to="'/acc/accounting/mod'"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field v-model="searchValue" prepend-inner-icon="mdi-magnify" density="compact" hide-details :rounded="false"
|
||||||
v-model="searchValue"
|
placeholder="جست و جو ..."></v-text-field>
|
||||||
prepend-inner-icon="mdi-magnify"
|
|
||||||
density="compact"
|
|
||||||
hide-details
|
|
||||||
:rounded="false"
|
|
||||||
placeholder="جست و جو ..."
|
|
||||||
></v-text-field>
|
|
||||||
|
|
||||||
<v-data-table
|
<v-data-table :headers="headers" :items="filteredItems" :search="searchValue" :loading="loading"
|
||||||
:headers="headers"
|
:header-props="{ class: 'custom-header' }" hover>
|
||||||
:items="filteredItems"
|
|
||||||
:search="searchValue"
|
|
||||||
:loading="loading"
|
|
||||||
:header-props="{ class: 'custom-header' }"
|
|
||||||
hover
|
|
||||||
>
|
|
||||||
<template v-slot:item.state="{ item }">
|
<template v-slot:item.state="{ item }">
|
||||||
<v-icon
|
<v-icon :color="item.type !== 'calc' ? 'error' : 'success'">
|
||||||
:color="item.type !== 'accounting' ? 'error' : 'success'"
|
{{ item.type !== 'calc' ? 'mdi-lock' : 'mdi-lock-open' }}
|
||||||
>
|
|
||||||
{{ item.type !== 'accounting' ? 'mdi-lock' : 'mdi-lock-open' }}
|
|
||||||
</v-icon>
|
</v-icon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:item.operation="{ item }">
|
<template v-slot:item.operation="{ item }">
|
||||||
<v-tooltip text="مشاهده سند" location="bottom">
|
<v-tooltip v-if="!isPluginActive('accpro') || item.type !== 'calc'" text="مشاهده سند" location="bottom">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn
|
<v-btn v-bind="props" icon variant="text" color="success" :to="'/acc/accounting/view/' + item.code">
|
||||||
v-bind="props"
|
|
||||||
icon
|
|
||||||
variant="text"
|
|
||||||
color="success"
|
|
||||||
:to="'/acc/accounting/view/' + item.code"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-eye</v-icon>
|
<v-icon>mdi-eye</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
|
<v-menu v-else-if="item.type === 'calc' && isPluginActive('accpro')">
|
||||||
|
<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 :title="$t('dialog.view')" :to="'/acc/accounting/view/' + item.code">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="green-darken-4" icon="mdi-eye"></v-icon>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item :title="$t('dialog.edit')" :to="'/acc/accounting/mod/' + item.id">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon icon="mdi-file-edit"></v-icon>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item :title="$t('dialog.delete')" @click="openDeleteDialog(item)">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="deep-orange-accent-4" icon="mdi-trash-can"></v-icon>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
|
|
||||||
|
<!-- دیالوگ تأیید حذف -->
|
||||||
|
<v-dialog v-model="deleteDialog" max-width="500">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title class="d-flex align-center pa-4">
|
||||||
|
<v-icon color="error" size="large" class="ml-2">mdi-alert-circle-outline</v-icon>
|
||||||
|
<span class="text-h5 font-weight-bold">حذف سند حسابداری</span>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-text class="pa-4">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<div class="text-subtitle-1 mb-2">آیا مطمئن هستید که میخواهید سند زیر را حذف کنید؟</div>
|
||||||
|
|
||||||
|
<v-card variant="outlined" class="mt-2">
|
||||||
|
<v-card-text>
|
||||||
|
<div class="d-flex justify-space-between mb-2">
|
||||||
|
<span class="text-subtitle-2 font-weight-bold">کد سند:</span>
|
||||||
|
<span>{{ selectedItem?.code?.toLocaleString() }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-space-between mb-2">
|
||||||
|
<span class="text-subtitle-2 font-weight-bold">تاریخ:</span>
|
||||||
|
<span>{{ selectedItem?.date }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-space-between mb-2">
|
||||||
|
<span class="text-subtitle-2 font-weight-bold">شرح:</span>
|
||||||
|
<span>{{ selectedItem?.des }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-space-between">
|
||||||
|
<span class="text-subtitle-2 font-weight-bold">مبلغ:</span>
|
||||||
|
<span>{{ selectedItem?.amountRaw?.toLocaleString() }}</span>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-alert
|
||||||
|
v-if="selectedItem?.type !== 'calc'"
|
||||||
|
type="warning"
|
||||||
|
variant="tonal"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="warning">mdi-alert</v-icon>
|
||||||
|
</template>
|
||||||
|
این سند قفل شده است و امکان حذف آن وجود ندارد.
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions class="pa-4">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="grey-darken-1"
|
||||||
|
variant="text"
|
||||||
|
@click="deleteDialog = false"
|
||||||
|
:disabled="deleteLoading"
|
||||||
|
>
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
variant="tonal"
|
||||||
|
@click="confirmDelete"
|
||||||
|
:loading="deleteLoading"
|
||||||
|
:disabled="selectedItem?.type !== 'calc'"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</template>
|
||||||
|
حذف سند
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- اسنکبار برای نمایش پیام -->
|
||||||
|
<v-snackbar v-model="snackbar.show" :color="snackbar.color" timeout="3000">
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn variant="text" @click="snackbar.show = false">
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -65,6 +157,15 @@ import axios from 'axios'
|
||||||
const searchValue = ref('')
|
const searchValue = ref('')
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
|
const deleteDialog = ref(false)
|
||||||
|
const deleteLoading = ref(false)
|
||||||
|
const selectedItem = ref(null)
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
color: 'success'
|
||||||
|
})
|
||||||
|
const plugins = ref({})
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
{ title: 'وضعیت', key: 'state', sortable: true },
|
{ title: 'وضعیت', key: 'state', sortable: true },
|
||||||
|
@ -76,6 +177,10 @@ const headers = [
|
||||||
{ title: 'ثبت کننده', key: 'submitter', sortable: true }
|
{ title: 'ثبت کننده', key: 'submitter', sortable: true }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const isPluginActive = (plugName) => {
|
||||||
|
return plugins.value[plugName] !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/api/accounting/search', {
|
const response = await axios.post('/api/accounting/search', {
|
||||||
|
@ -93,6 +198,15 @@ const loadData = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadPlugins = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/plugin/get/actives')
|
||||||
|
plugins.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading plugins:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
if (!searchValue.value) return items.value
|
if (!searchValue.value) return items.value
|
||||||
|
|
||||||
|
@ -117,8 +231,51 @@ const filteredItems = computed(() => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const openDeleteDialog = (item) => {
|
||||||
|
selectedItem.value = item
|
||||||
|
deleteDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
try {
|
||||||
|
deleteLoading.value = true
|
||||||
|
const response = await axios.delete(`/api/hesabdari/direct/doc/delete/${selectedItem.value.id}`)
|
||||||
|
if (response.data.success) {
|
||||||
|
// حذف آیتم از لیست
|
||||||
|
const index = items.value.findIndex(item => item.id === selectedItem.value.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
items.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
deleteDialog.value = false
|
||||||
|
// نمایش پیام موفقیت
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'سند با موفقیت حذف شد',
|
||||||
|
color: 'success'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// نمایش پیام خطا
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: response.data.message || 'خطا در حذف سند',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// نمایش پیام خطا
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: error.response?.data?.message || 'خطا در ارتباط با سرور',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
deleteLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData()
|
loadData()
|
||||||
|
loadPlugins()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<v-toolbar color="toolbar" :title="$t('dialog.accounting_doc')">
|
<v-toolbar color="toolbar" :title="$t('dialog.accounting_doc')">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||||
|
@ -40,6 +39,13 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<v-alert v-if="error" type="error" class="mt-4">
|
||||||
|
{{ error }}
|
||||||
|
<template v-slot:close>
|
||||||
|
<v-btn icon="mdi-close" variant="text" @click="error = null"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
<v-table class="border rounded d-none d-sm-table mt-3" style="width: 100%;">
|
<v-table class="border rounded d-none d-sm-table mt-3" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="background-color: #0D47A1; color: white; height: 40px;">
|
<tr style="background-color: #0D47A1; color: white; height: 40px;">
|
||||||
|
@ -52,6 +58,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<template v-if="loading">
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center pa-4">
|
||||||
|
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||||||
|
<span class="mr-2">در حال بارگذاری...</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<template v-for="(row, index) in form.rows" :key="index">
|
<template v-for="(row, index) in form.rows" :key="index">
|
||||||
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '40px' }">
|
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '40px' }">
|
||||||
<td class="text-center" style="min-width: 150px; padding: 0 4px;">
|
<td class="text-center" style="min-width: 150px; padding: 0 4px;">
|
||||||
|
@ -59,9 +74,93 @@
|
||||||
v-model="row.ref"
|
v-model="row.ref"
|
||||||
:rules="[v => !!v || 'حساب الزامی است']"
|
:rules="[v => !!v || 'حساب الزامی است']"
|
||||||
@account-selected="(account) => handleAccountSelect(row, account)"
|
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||||
|
@tableType="(type) => handleTableType(row, type)"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" style="min-width: 100px; padding: 0 4px;">
|
<td class="text-center" style="min-width: 150px; padding: 0 4px;">
|
||||||
|
<template v-if="row.tableType === 'bank'">
|
||||||
|
<Hbankaccountsearch
|
||||||
|
v-model="row.bankAccount"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handleBankAccountSelect(row, value)"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
:ref="`bankAccount_${row.ref}`"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="row.tableType === 'cashdesk'">
|
||||||
|
<Hcashdesksearch
|
||||||
|
v-model="row.cashdesk"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handleCashdeskSelect(row, value)"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
:ref="`cashdesk_${row.ref}`"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="row.tableType === 'salary'">
|
||||||
|
<Hsalarysearch
|
||||||
|
v-model="row.salary"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handleSalarySelect(row, value)"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
:ref="`salary_${row.ref}`"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="row.tableType === 'person'">
|
||||||
|
<Hpersonsearch
|
||||||
|
v-model="row.person"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handlePersonSelect(row, value)"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
:ref="`person_${row.ref}`"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="row.tableType === 'commodity'">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<Hcommoditysearch
|
||||||
|
v-model="row.commodity"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handleCommoditySelect(row, value)"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
:ref="`commodity_${row.ref}`"
|
||||||
|
:key="row.ref"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="row.commodityCount"
|
||||||
|
label="تعداد"
|
||||||
|
type="number"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
class="my-0 mr-2"
|
||||||
|
style="font-size: 0.7rem; width: 80px;"
|
||||||
|
min="0"
|
||||||
|
></v-text-field>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-text-field
|
||||||
|
v-model="row.detail"
|
||||||
|
label="تفصیل"
|
||||||
|
density="compact"
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
hide-details
|
||||||
|
></v-text-field>
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" style="padding: 0 4px;">
|
<td class="text-center" style="padding: 0 4px;">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
@ -74,28 +173,26 @@
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||||
<v-text-field
|
<Hnumberinput
|
||||||
v-model="row.bd"
|
v-model="row.bd"
|
||||||
label="بدهکار"
|
label="بدهکار"
|
||||||
type="number"
|
|
||||||
density="compact"
|
density="compact"
|
||||||
@input="calculateTotals"
|
@input="calculateTotals"
|
||||||
class="my-0"
|
class="my-0"
|
||||||
style="font-size: 0.7rem;"
|
style="font-size: 0.7rem;"
|
||||||
hide-details
|
hide-details
|
||||||
></v-text-field>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||||
<v-text-field
|
<Hnumberinput
|
||||||
v-model="row.bs"
|
v-model="row.bs"
|
||||||
label="بستانکار"
|
label="بستانکار"
|
||||||
type="number"
|
|
||||||
density="compact"
|
density="compact"
|
||||||
@input="calculateTotals"
|
@input="calculateTotals"
|
||||||
class="my-0"
|
class="my-0"
|
||||||
style="font-size: 0.7rem;"
|
style="font-size: 0.7rem;"
|
||||||
hide-details
|
hide-details
|
||||||
></v-text-field>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" style="width: 50px; padding: 0 4px;">
|
<td class="text-center" style="width: 50px; padding: 0 4px;">
|
||||||
<v-tooltip text="حذف" location="bottom">
|
<v-tooltip text="حذف" location="bottom">
|
||||||
|
@ -112,11 +209,21 @@
|
||||||
<v-btn color="primary" prepend-icon="mdi-plus" size="x-small" @click="addRow">افزودن سطر جدید</v-btn>
|
<v-btn color="primary" prepend-icon="mdi-plus" size="x-small" @click="addRow">افزودن سطر جدید</v-btn>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|
||||||
<!-- جدول موبایل -->
|
<!-- جدول موبایل -->
|
||||||
<div class="d-sm-none">
|
<div class="d-sm-none">
|
||||||
|
<template v-if="loading">
|
||||||
|
<v-card class="mb-4" variant="outlined">
|
||||||
|
<v-card-text class="text-center">
|
||||||
|
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||||||
|
<span class="mr-2">در حال بارگذاری...</span>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<v-card v-for="(row, index) in form.rows" :key="index" class="mb-4" variant="outlined">
|
<v-card v-for="(row, index) in form.rows" :key="index" class="mb-4" variant="outlined">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div class="d-flex justify-space-between align-center mb-2">
|
<div class="d-flex justify-space-between align-center mb-2">
|
||||||
|
@ -130,6 +237,27 @@
|
||||||
@account-selected="(account) => handleAccountSelect(row, account)"
|
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<Hbankaccountsearch
|
||||||
|
v-model="row.bankAccount"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handleBankAccountSelect(row, value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<Hcashdesksearch
|
||||||
|
v-model="row.cashdesk"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handleCashdeskSelect(row, value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<Hpersonsearch
|
||||||
|
v-model="row.person"
|
||||||
|
:rules="[]"
|
||||||
|
@update:modelValue="(value) => handlePersonSelect(row, value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="row.des"
|
v-model="row.des"
|
||||||
|
@ -141,26 +269,24 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-space-between mb-2">
|
<div class="d-flex justify-space-between mb-2">
|
||||||
<div style="width: 48%;">
|
<div style="width: 48%;">
|
||||||
<v-text-field
|
<Hnumberinput
|
||||||
v-model="row.bd"
|
v-model="row.bd"
|
||||||
label="بدهکار"
|
label="بدهکار"
|
||||||
type="number"
|
|
||||||
density="compact"
|
density="compact"
|
||||||
@input="calculateTotals"
|
@input="calculateTotals"
|
||||||
class="my-0"
|
class="my-0"
|
||||||
style="font-size: 0.8rem;"
|
style="font-size: 0.8rem;"
|
||||||
></v-text-field>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 48%;">
|
<div style="width: 48%;">
|
||||||
<v-text-field
|
<Hnumberinput
|
||||||
v-model="row.bs"
|
v-model="row.bs"
|
||||||
label="بستانکار"
|
label="بستانکار"
|
||||||
type="number"
|
|
||||||
density="compact"
|
density="compact"
|
||||||
@input="calculateTotals"
|
@input="calculateTotals"
|
||||||
class="my-0"
|
class="my-0"
|
||||||
style="font-size: 0.8rem;"
|
style="font-size: 0.8rem;"
|
||||||
></v-text-field>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
@ -169,13 +295,14 @@
|
||||||
<v-btn icon="mdi-delete" variant="text" color="error" @click="removeRow(row)"></v-btn>
|
<v-btn icon="mdi-delete" variant="text" color="error" @click="removeRow(row)"></v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
</template>
|
||||||
<v-btn color="primary" prepend-icon="mdi-plus" block class="mb-4" @click="addRow">افزودن ردیف جدید</v-btn>
|
<v-btn color="primary" prepend-icon="mdi-plus" block class="mb-4" @click="addRow">افزودن ردیف جدید</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-row class="mt-4">
|
<v-row class="mt-4">
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:value="totalBd"
|
v-model="totalBd"
|
||||||
label="جمع بدهکار"
|
label="جمع بدهکار"
|
||||||
readonly
|
readonly
|
||||||
dense
|
dense
|
||||||
|
@ -183,15 +310,13 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:value="totalBs"
|
v-model="totalBs"
|
||||||
label="جمع بستانکار"
|
label="جمع بستانکار"
|
||||||
readonly
|
readonly
|
||||||
dense
|
dense
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-alert v-if="error" type="error" class="mt-4">{{ error }}</v-alert>
|
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
|
@ -215,6 +340,23 @@
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Snackbar برای نمایش پیامها -->
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -222,17 +364,23 @@ import axios from 'axios';
|
||||||
import moment from 'jalali-moment';
|
import moment from 'jalali-moment';
|
||||||
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
|
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
|
||||||
import Haccountsearch from '@/components/forms/Haccountsearch.vue';
|
import Haccountsearch from '@/components/forms/Haccountsearch.vue';
|
||||||
|
import Hbankaccountsearch from '@/components/forms/Hbankaccountsearch.vue';
|
||||||
|
import Hcashdesksearch from '@/components/forms/Hcashdesksearch.vue';
|
||||||
|
import Hsalarysearch from '@/components/forms/Hsalarysearch.vue';
|
||||||
|
import Hcommoditysearch from '@/components/forms/Hcommoditysearch.vue';
|
||||||
|
import Hpersonsearch from '@/components/forms/Hpersonsearch.vue';
|
||||||
|
import Hnumberinput from '@/components/forms/Hnumberinput.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Hdatepicker,
|
Hdatepicker,
|
||||||
Haccountsearch
|
Haccountsearch,
|
||||||
},
|
Hbankaccountsearch,
|
||||||
props: {
|
Hcashdesksearch,
|
||||||
docId: {
|
Hsalarysearch,
|
||||||
type: Number,
|
Hcommoditysearch,
|
||||||
default: null,
|
Hpersonsearch,
|
||||||
},
|
Hnumberinput
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -240,54 +388,117 @@ export default {
|
||||||
date: '',
|
date: '',
|
||||||
des: '',
|
des: '',
|
||||||
rows: [
|
rows: [
|
||||||
{ ref: null, refName: '', bd: '0', bs: '0', des: '', selectedAccounts: [] },
|
{ ref: null, refName: '', bd: '0', bs: '0', des: '', detail: '', selectedAccounts: [], bankAccount: null, cashdesk: null, salary: null, commodity: null, commodityCount: null, person: null, tableType: null },
|
||||||
|
{ ref: null, refName: '', bd: '0', bs: '0', des: '', detail: '', selectedAccounts: [], bankAccount: null, cashdesk: null, salary: null, commodity: null, commodityCount: null, person: null, tableType: null },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
hesabdariTables: [],
|
|
||||||
totalBd: 0,
|
totalBd: 0,
|
||||||
totalBs: 0,
|
totalBs: 0,
|
||||||
error: null,
|
error: null,
|
||||||
deleteDialog: false,
|
deleteDialog: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
snackbar: {
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
computed: {
|
||||||
this.fetchHesabdariTables();
|
docId() {
|
||||||
if (this.docId) {
|
return this.$route.params.id;
|
||||||
this.fetchDoc();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loading = true;
|
||||||
|
Promise.all([
|
||||||
|
this.docId ? this.fetchDoc() : Promise.resolve()
|
||||||
|
]).finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchHesabdariTables() {
|
showSnackbar(text, color = 'success') {
|
||||||
try {
|
this.snackbar.text = text;
|
||||||
const response = await axios.get('/api/hesabdari/tables');
|
this.snackbar.color = color;
|
||||||
this.hesabdariTables = response.data.data;
|
this.snackbar.show = true;
|
||||||
} catch (error) {
|
|
||||||
console.error('خطا در دریافت حسابها:', error.response?.data || error.message);
|
|
||||||
this.error = 'خطا در بارگذاری حسابها: ' + (error.response?.data?.message || 'مشکل ناشناخته');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async fetchDoc() {
|
async fetchDoc() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/hesabdari/doc/${this.docId}`);
|
const response = await axios.get(`/api/hesabdari/direct/doc/get/${this.docId}`);
|
||||||
|
if (response.data.success) {
|
||||||
const serverDate = response.data.data.date;
|
const serverDate = response.data.data.date;
|
||||||
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY-MM-DD');
|
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY/MM/DD');
|
||||||
this.form.des = response.data.data.des || '';
|
this.form.des = response.data.data.des || '';
|
||||||
this.form.rows = response.data.data.rows.map(row => ({
|
|
||||||
|
// ایجاد یک آرایه موقت برای ذخیره ردیفها
|
||||||
|
const tempRows = response.data.data.rows.map(row => ({
|
||||||
ref: row.ref.id,
|
ref: row.ref.id,
|
||||||
refName: row.ref.name,
|
refName: row.ref.name,
|
||||||
bd: row.bd,
|
bd: row.bd,
|
||||||
bs: row.bs,
|
bs: row.bs,
|
||||||
des: row.des,
|
des: row.des,
|
||||||
|
detail: row.detail || '',
|
||||||
selectedAccounts: [{ id: row.ref.id, name: row.ref.name }],
|
selectedAccounts: [{ id: row.ref.id, name: row.ref.name }],
|
||||||
|
bankAccount: row.bankAccount,
|
||||||
|
cashdesk: row.cashdesk,
|
||||||
|
salary: row.salary,
|
||||||
|
commodity: row.commodity,
|
||||||
|
commodityCount: row.commodityCount,
|
||||||
|
person: row.person,
|
||||||
|
tableType: row.ref.tableType
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// یکباره تمام ردیفها را تنظیم کنیم
|
||||||
|
this.form.rows = tempRows;
|
||||||
|
|
||||||
|
// تنظیم مقادیر کامپوننتهای فرزند
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
|
// استفاده از Promise.all برای اجرای همزمان تنظیم مقادیر
|
||||||
|
await Promise.all(this.form.rows.map(async (row) => {
|
||||||
|
if (row.tableType === 'bank' && row.bankAccount) {
|
||||||
|
const bankAccountRef = this.$refs[`bankAccount_${row.ref}`]?.[0];
|
||||||
|
if (bankAccountRef) {
|
||||||
|
await bankAccountRef.setValue(row.bankAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row.tableType === 'cashdesk' && row.cashdesk) {
|
||||||
|
const cashdeskRef = this.$refs[`cashdesk_${row.ref}`]?.[0];
|
||||||
|
if (cashdeskRef) {
|
||||||
|
await cashdeskRef.setValue(row.cashdesk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row.tableType === 'person' && row.person) {
|
||||||
|
const personRef = this.$refs[`person_${row.ref}`]?.[0];
|
||||||
|
if (personRef) {
|
||||||
|
await personRef.setValue(row.person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row.tableType === 'commodity' && row.commodity) {
|
||||||
|
const commodityRef = this.$refs[`commodity_${row.ref}`]?.[0];
|
||||||
|
if (commodityRef) {
|
||||||
|
await commodityRef.setValue(row.commodity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row.tableType === 'salary' && row.salary) {
|
||||||
|
const salaryRef = this.$refs[`salary_${row.ref}`]?.[0];
|
||||||
|
if (salaryRef) {
|
||||||
|
await salaryRef.setValue(row.salary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
this.calculateTotals();
|
this.calculateTotals();
|
||||||
|
} else {
|
||||||
|
this.error = response.data.message || 'خطا در بارگذاری سند';
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error = 'خطا در بارگذاری سند: ' + (error.response?.data?.message || 'مشکل ناشناخته');
|
this.error = 'خطا در بارگذاری سند: ' + (error.response?.data?.message || 'مشکل ناشناخته');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addRow() {
|
addRow() {
|
||||||
this.form.rows.push({ ref: null, refName: '', bd: '0', bs: '0', des: '', selectedAccounts: [] });
|
this.form.rows.push({ ref: null, refName: '', bd: '0', bs: '0', des: '', detail: '', selectedAccounts: [], bankAccount: null, cashdesk: null, salary: null, commodity: null, commodityCount: null, person: null, tableType: null });
|
||||||
},
|
},
|
||||||
removeRow(item) {
|
removeRow(item) {
|
||||||
const index = this.form.rows.indexOf(item);
|
const index = this.form.rows.indexOf(item);
|
||||||
|
@ -297,46 +508,180 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
calculateTotals() {
|
calculateTotals() {
|
||||||
|
let hasError = false;
|
||||||
|
for (const row of this.form.rows) {
|
||||||
|
if (!this.validateDebitCredit(row)) {
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.error = null;
|
||||||
this.totalBd = this.form.rows.reduce((sum, row) => sum + parseInt(row.bd || 0), 0);
|
this.totalBd = this.form.rows.reduce((sum, row) => sum + parseInt(row.bd || 0), 0);
|
||||||
this.totalBs = this.form.rows.reduce((sum, row) => sum + parseInt(row.bs || 0), 0);
|
this.totalBs = this.form.rows.reduce((sum, row) => sum + parseInt(row.bs || 0), 0);
|
||||||
},
|
},
|
||||||
selectAccount(row, selected) {
|
validateDebitCredit(row) {
|
||||||
if (selected.length > 0) {
|
if (parseInt(row.bd) > 0 && parseInt(row.bs) > 0) {
|
||||||
const account = selected[0];
|
this.error = 'در هر سطر فقط یکی از فیلدهای بدهکار یا بستانکار میتواند مقدار داشته باشد';
|
||||||
|
// صفر کردن مقدار نامعتبر
|
||||||
|
if (row.bd > 0) {
|
||||||
|
row.bd = '0';
|
||||||
|
} else if (row.bs > 0) {
|
||||||
|
row.bs = '0';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
handleAccountSelect(row, account) {
|
||||||
row.ref = account.id;
|
row.ref = account.id;
|
||||||
row.refName = account.name;
|
row.refName = account.name;
|
||||||
row.selectedAccounts = [account];
|
row.selectedAccounts = [account];
|
||||||
|
// فقط tableType را تنظیم کنید اگر تغییر کرده باشد
|
||||||
|
if (row.tableType !== account.tableType) {
|
||||||
|
this.handleTableType(row, account.tableType);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleBankAccountSelect(row, bankAccount) {
|
||||||
|
row.bankAccount = bankAccount;
|
||||||
|
},
|
||||||
|
handleCashdeskSelect(row, cashdesk) {
|
||||||
|
row.cashdesk = cashdesk;
|
||||||
|
},
|
||||||
|
handleSalarySelect(row, salary) {
|
||||||
|
row.salary = salary;
|
||||||
|
},
|
||||||
|
handleCommoditySelect(row, commodity) {
|
||||||
|
row.commodity = commodity;
|
||||||
|
row.commodityCount = commodity ? row.commodityCount : null;
|
||||||
|
},
|
||||||
|
handlePersonSelect(row, person) {
|
||||||
|
row.person = person;
|
||||||
|
},
|
||||||
|
handleTableType(row, type) {
|
||||||
|
if (row.tableType === type) return; // جلوگیری از تغییرات غیرضروری
|
||||||
|
const prevCommodity = row.commodity; // ذخیره مقدار قبلی commodity
|
||||||
|
const prevCommodityCount = row.commodityCount;
|
||||||
|
|
||||||
|
row.tableType = type;
|
||||||
|
// فقط فیلدهای غیرمرتبط را پاک کنید
|
||||||
|
if (type !== 'bank') row.bankAccount = null;
|
||||||
|
if (type !== 'cashdesk') row.cashdesk = null;
|
||||||
|
if (type !== 'person') row.person = null;
|
||||||
|
if (type !== 'salary') row.salary = null;
|
||||||
|
if (type !== 'commodity') {
|
||||||
|
row.commodity = null;
|
||||||
|
row.commodityCount = null;
|
||||||
|
} else {
|
||||||
|
// بازیابی commodity اگر tableType به commodity برگردد
|
||||||
|
row.commodity = prevCommodity;
|
||||||
|
row.commodityCount = prevCommodityCount;
|
||||||
|
}
|
||||||
|
if (type !== 'calc') row.detail = '';
|
||||||
|
},
|
||||||
async submitForm() {
|
async submitForm() {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
if (this.form.rows.length < 2) {
|
||||||
|
this.error = 'حداقل باید دو سطر در سند وجود داشته باشد';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.totalBd !== this.totalBs) {
|
if (this.totalBd !== this.totalBs) {
|
||||||
this.error = 'جمع بدهکار و بستانکار باید برابر باشد';
|
this.error = 'جمع بدهکار و بستانکار باید برابر باشد';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const row of this.form.rows) {
|
||||||
|
if (!row.ref) {
|
||||||
|
this.error = 'انتخاب حساب در تمام سطرها الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(row.bd) === 0 && parseInt(row.bs) === 0) {
|
||||||
|
this.error = 'در هر سطر باید حداقل یکی از فیلدهای بدهکار یا بستانکار مقدار داشته باشد';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.validateDebitCredit(row)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.tableType === 'bank' && !row.bankAccount) {
|
||||||
|
this.error = 'انتخاب حساب بانکی در سطر مربوطه الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.tableType === 'cashdesk' && !row.cashdesk) {
|
||||||
|
this.error = 'انتخاب صندوق در سطر مربوطه الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.tableType === 'salary' && !row.salary) {
|
||||||
|
this.error = 'انتخاب حقوق در سطر مربوطه الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.tableType === 'person' && !row.person) {
|
||||||
|
this.error = 'انتخاب شخص در سطر مربوطه الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.tableType === 'commodity' && !row.commodity) {
|
||||||
|
this.error = 'انتخاب کالا در سطر مربوطه الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.tableType === 'commodity' && !row.commodityCount) {
|
||||||
|
this.error = 'تعداد کالا در سطر مربوطه الزامی است';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
date: moment(this.form.date, 'YYYY-MM-DD').locale('fa').format('YYYY/MM/DD'),
|
date: this.form.date,
|
||||||
des: this.form.des,
|
des: this.form.des,
|
||||||
rows: this.form.rows.map(row => ({
|
rows: this.form.rows.map(row => ({
|
||||||
ref: row.ref,
|
ref: row.ref,
|
||||||
bd: row.bd,
|
bd: row.bd,
|
||||||
bs: row.bs,
|
bs: row.bs,
|
||||||
des: row.des,
|
des: row.des,
|
||||||
|
detail: row.detail,
|
||||||
|
bankAccount: row.bankAccount,
|
||||||
|
cashdesk: row.cashdesk,
|
||||||
|
salary: row.salary,
|
||||||
|
commodity: row.commodity,
|
||||||
|
commodityCount: row.commodityCount,
|
||||||
|
person: row.person
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
if (this.docId) {
|
if (this.docId) {
|
||||||
await axios.put(`/api/hesabdari/doc/${this.docId}`, payload);
|
response = await axios.put(`/api/hesabdari/direct/doc/update/${this.docId}`, payload);
|
||||||
this.$emit('saved', 'سند با موفقیت ویرایش شد');
|
|
||||||
} else {
|
} else {
|
||||||
const response = await axios.post('/api/hesabdari/doc', payload);
|
response = await axios.post('/api/hesabdari/direct/doc/create', payload);
|
||||||
this.$emit('saved', 'سند با موفقیت ثبت شد', response.data.data.id);
|
}
|
||||||
|
|
||||||
|
if (response && response.data && response.data.success) {
|
||||||
|
this.showSnackbar(response.data.message);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$router.push('/acc/accounting/list');
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
this.error = response?.data?.message || 'خطا در انجام عملیات';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error = error.response?.data?.message || 'خطا در ثبت سند';
|
if (error.response && error.response.data && error.response.data.success) {
|
||||||
|
this.showSnackbar(error.response.data.message);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$router.push('/acc/accounting/list');
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
this.error = error.response?.data?.message || 'خطا در ارتباط با سرور';
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
@ -344,10 +689,17 @@ export default {
|
||||||
async confirmDelete() {
|
async confirmDelete() {
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
await axios.delete(`/api/hesabdari/doc/${this.docId}`);
|
const response = await axios.delete(`/api/hesabdari/direct/doc/delete/${this.docId}`);
|
||||||
|
if (response && response.data && response.data.success) {
|
||||||
|
this.showSnackbar(response.data.message);
|
||||||
|
setTimeout(() => {
|
||||||
this.$router.push('/acc/accounting/list');
|
this.$router.push('/acc/accounting/list');
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
this.error = response?.data?.message || 'خطا در حذف سند';
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error = 'خطا در حذف سند';
|
this.error = error.response?.data?.message || 'خطا در حذف سند';
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
|
@ -1,22 +1,237 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import { defineComponent } from 'vue'
|
<v-toolbar color="toolbar" title="حواله ورود به انبار">
|
||||||
import axios from "axios";
|
<template v-slot:prepend>
|
||||||
import Loading from "vue-loading-overlay";
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
import 'vue-loading-overlay/dist/css/index.css';
|
<template v-slot:activator="{ props }">
|
||||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
<v-tooltip text="تکمیل خودکار" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
@click="autofill"
|
||||||
|
variant="text"
|
||||||
|
icon="mdi-auto-fix"
|
||||||
|
class="mx-2"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ثبت حواله ورود" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
|
color="success"
|
||||||
|
icon="mdi-content-save"
|
||||||
|
@click="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
import Swal from "sweetalert2";
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.date"
|
||||||
|
label="تاریخ"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.store.des"
|
||||||
|
label="انبار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.person.des"
|
||||||
|
label="خریدار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
export default defineComponent({
|
<v-row>
|
||||||
name: "buy",
|
<v-col cols="12">
|
||||||
components: {
|
<v-text-field
|
||||||
Loading,
|
v-model="ticket.des"
|
||||||
},
|
label="شرح"
|
||||||
data: () => {
|
variant="outlined"
|
||||||
return {
|
density="compact"
|
||||||
loading: false,
|
/>
|
||||||
doc: {},
|
</v-col>
|
||||||
ticket: {
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.transfer"
|
||||||
|
label="حملونقل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.receiver"
|
||||||
|
label="تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-select
|
||||||
|
v-model="ticket.transferType"
|
||||||
|
:items="transferTypes"
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
label="روش تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.referral"
|
||||||
|
label="شماره پیگیری"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="items"
|
||||||
|
:loading="loading"
|
||||||
|
class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }"
|
||||||
|
density="compact"
|
||||||
|
>
|
||||||
|
<template v-slot:item.commdityCount="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].ticketCount"
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:min="0"
|
||||||
|
:max="item.remain"
|
||||||
|
@blur="(event) => { if (items[index].ticketCount === '') { items[index].ticketCount = 0 } }"
|
||||||
|
@keypress="isNumber($event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.des="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].des"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.referal="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].referral"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
interface TransferType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
des: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
des: string;
|
||||||
|
name: string;
|
||||||
|
manager: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Commodity {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
commdityCount: number;
|
||||||
|
docCount: number;
|
||||||
|
countBefore: number;
|
||||||
|
remain: number;
|
||||||
|
ticketCount: number;
|
||||||
|
des: string;
|
||||||
|
referral: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
type: string;
|
||||||
|
typeString: string;
|
||||||
|
date: string;
|
||||||
|
des: string;
|
||||||
|
transfer: string;
|
||||||
|
receiver: string;
|
||||||
|
code: string;
|
||||||
|
store: Store;
|
||||||
|
person: Person;
|
||||||
|
transferType: TransferType;
|
||||||
|
referral: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Year {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
now: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const doc = ref({})
|
||||||
|
const ticket = ref<Ticket>({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
typeString: 'حواله ورود',
|
typeString: 'حواله ورود',
|
||||||
date: '',
|
date: '',
|
||||||
|
@ -24,226 +239,162 @@ export default defineComponent({
|
||||||
transfer: '',
|
transfer: '',
|
||||||
receiver: '',
|
receiver: '',
|
||||||
code: '',
|
code: '',
|
||||||
store: {},
|
store: {} as Store,
|
||||||
person: {},
|
person: {} as Person,
|
||||||
transferType: {},
|
transferType: {} as TransferType,
|
||||||
referral: ''
|
referral: ''
|
||||||
},
|
|
||||||
transferTypes: [],
|
|
||||||
year: {},
|
|
||||||
items: [],
|
|
||||||
headers: [
|
|
||||||
{ text: "کد", value: "commodity.code" },
|
|
||||||
{ text: "کالا", value: "commodity.name", sortable: true },
|
|
||||||
{ text: "واحد", value: "commodity.unit", sortable: true },
|
|
||||||
{ text: "مورد نیاز", value: "docCount" },
|
|
||||||
{ text: "از قبل", value: "countBefore" },
|
|
||||||
{ text: "باقیمانده", value: "remain" },
|
|
||||||
{ text: "تعداد", value: "commdityCount", sortable: true },
|
|
||||||
{ text: "ارجاع", value: "referal", sortable: true },
|
|
||||||
{ text: "توضیحات", value: "des" },
|
|
||||||
],
|
|
||||||
currencyConfig: {
|
|
||||||
masked: false,
|
|
||||||
prefix: '',
|
|
||||||
suffix: '',
|
|
||||||
thousands: ',',
|
|
||||||
decimal: '.',
|
|
||||||
precision: 0,
|
|
||||||
disableNegative: false,
|
|
||||||
disabled: false,
|
|
||||||
min: 0,
|
|
||||||
max: null,
|
|
||||||
allowBlank: false,
|
|
||||||
minimumNumberOfCharacters: 0,
|
|
||||||
shouldRound: true,
|
|
||||||
focusOnRight: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit() {
|
|
||||||
this.loading = true;
|
|
||||||
let errors = [];
|
|
||||||
let rowsWithZeroCount = 0;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
if (element.ticketCount === '') {
|
|
||||||
errors.push('تعداد کالا در ردیف ' + (index + 1) + 'وارد نشده است.');
|
|
||||||
}
|
|
||||||
else if (element.ticketCount === 0 && element.remain != 0) {
|
|
||||||
rowsWithZeroCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//check all values is zero
|
|
||||||
if (rowsWithZeroCount != 0) {
|
|
||||||
errors.push('تعداد تمام کالاها صفر است!');
|
|
||||||
}
|
|
||||||
if (errors.length != 0) {
|
|
||||||
let errorStr = '<ul>';
|
|
||||||
errors.forEach((item) => { errorStr += '<li>' + item + '</li>' })
|
|
||||||
errorStr += '</ul>'
|
|
||||||
Swal.fire({
|
|
||||||
html: errorStr,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//going to save ticket
|
|
||||||
axios.post('/api/storeroom/ticket/insert', {
|
|
||||||
doc: this.doc,
|
|
||||||
ticket: this.ticket,
|
|
||||||
items: this.items
|
|
||||||
}).then((resp) => {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'حواله انبار با موفقیت ثبت شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.$router.push('/acc/storeroom/tickets/list');
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autofill() {
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = this.items[index].docCount;
|
|
||||||
this.items[index].des = 'تعداد ' + this.items[index].ticketCount + 'مورد تحویل شد. ';
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const transferTypes = ref<TransferType[]>([])
|
||||||
|
const year = ref<Year>({} as Year)
|
||||||
|
const items = ref<Commodity[]>([])
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ title: "کد", key: "commodity.code" },
|
||||||
|
{ title: "کالا", key: "commodity.name", sortable: true },
|
||||||
|
{ title: "واحد", key: "commodity.unit", sortable: true },
|
||||||
|
{ title: "مورد نیاز", key: "docCount" },
|
||||||
|
{ title: "از قبل", key: "countBefore" },
|
||||||
|
{ title: "باقیمانده", key: "remain" },
|
||||||
|
{ title: "تعداد", key: "commdityCount", sortable: true },
|
||||||
|
{ title: "ارجاع", key: "referal", sortable: true },
|
||||||
|
{ title: "توضیحات", key: "des" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
color: 'primary' as 'primary' | 'error' | 'success' | 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const submit = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const errors: string[] = []
|
||||||
|
let rowsWithZeroCount = 0
|
||||||
|
let totalCount = 0
|
||||||
|
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
if (element.ticketCount === 0) {
|
||||||
|
rowsWithZeroCount++
|
||||||
|
} else if (element.ticketCount === undefined || element.ticketCount === null) {
|
||||||
|
errors.push(`تعداد کالا در ردیف ${index + 1} وارد نشده است.`)
|
||||||
|
} else {
|
||||||
|
totalCount += element.ticketCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
errors.push('تعداد تمام کالاها صفر است!')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length !== 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: errors.join('\n'),
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||||
|
doc: doc.value,
|
||||||
|
ticket: {
|
||||||
|
...ticket.value,
|
||||||
|
senderTel: ticket.value.person.mobile || '',
|
||||||
|
sms: false
|
||||||
},
|
},
|
||||||
isNumber(evt: KeyboardEvent): void {
|
items: items.value
|
||||||
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
})
|
||||||
const keyPressed: string = evt.key;
|
|
||||||
|
if (response.data.result === 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||||
|
color: 'success'
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acc/storeroom/tickets/list')
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting form:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در ثبت اطلاعات',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const autofill = () => {
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
const remain = Math.max(0, items.value[index].remain)
|
||||||
|
items.value[index].ticketCount = remain
|
||||||
|
items.value[index].des = remain > 0 ? `تعداد ${remain} مورد تحویل شد.` : ''
|
||||||
|
items.value[index].type = 'input'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNumber = (evt: KeyboardEvent): void => {
|
||||||
|
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||||
|
const keyPressed: string = evt.key
|
||||||
if (!keysAllowed.includes(keyPressed)) {
|
if (!keysAllowed.includes(keyPressed)) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
loadData() {
|
|
||||||
axios.post('/api/storeroom/doc/get/info/' + this.$route.params.doc).then((res) => {
|
|
||||||
this.doc = res.data;
|
|
||||||
this.ticket.person = res.data.person;
|
|
||||||
this.ticket.des = 'حواله ورود انبار برای فاکتور خرید شماره # ' + this.doc.code;
|
|
||||||
this.items = res.data.commodities;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = 0;
|
|
||||||
this.items[index].docCount = element.commdityCount;
|
|
||||||
this.items[index].des = '';
|
|
||||||
this.items[index].type = 'input';
|
|
||||||
})
|
|
||||||
});
|
|
||||||
axios.post('/api/storeroom/info/' + this.$route.params.storeID).then((res) => {
|
|
||||||
this.ticket.store = res.data;
|
|
||||||
this.ticket.store.des = this.ticket.store.name + ' انباردار : ' + this.ticket.store.manager
|
|
||||||
});
|
|
||||||
//load year
|
|
||||||
axios.post('/api/year/get').then((response) => {
|
|
||||||
this.year = response.data;
|
|
||||||
this.ticket.date = response.data.now;
|
|
||||||
})
|
|
||||||
//load transfer types
|
|
||||||
axios.post('/api/storeroom/transfertype/list').then((response) => {
|
|
||||||
this.transferTypes = response.data;
|
|
||||||
this.ticket.transferType = response.data[0];
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.loadData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [docResponse, storeResponse, yearResponse, transferTypesResponse] = await Promise.all([
|
||||||
|
axios.post(`/api/storeroom/doc/get/info/${router.currentRoute.value.params.doc}`),
|
||||||
|
axios.post(`/api/storeroom/info/${router.currentRoute.value.params.storeID}`),
|
||||||
|
axios.post('/api/year/get'),
|
||||||
|
axios.post('/api/storeroom/transfertype/list')
|
||||||
|
])
|
||||||
|
|
||||||
|
doc.value = docResponse.data
|
||||||
|
ticket.value.person = docResponse.data.person
|
||||||
|
ticket.value.des = `حواله ورود انبار برای فاکتور خرید شماره # ${docResponse.data.code}`
|
||||||
|
items.value = docResponse.data.commodities.map((element: Commodity) => ({
|
||||||
|
...element,
|
||||||
|
ticketCount: 0,
|
||||||
|
docCount: element.commdityCount,
|
||||||
|
des: '',
|
||||||
|
type: 'input'
|
||||||
|
}))
|
||||||
|
|
||||||
|
ticket.value.store = storeResponse.data
|
||||||
|
ticket.value.store.des = `${storeResponse.data.name} انباردار : ${storeResponse.data.manager}`
|
||||||
|
|
||||||
|
year.value = yearResponse.data
|
||||||
|
ticket.value.date = yearResponse.data.now
|
||||||
|
|
||||||
|
transferTypes.value = transferTypesResponse.data
|
||||||
|
ticket.value.transferType = transferTypesResponse.data[0]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در بارگذاری دادهها',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<style scoped>
|
||||||
<div class="block block-content-full ">
|
.v-data-table {
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
border-radius: 8px;
|
||||||
<h3 class="block-title text-primary-dark">
|
}
|
||||||
<button @click="$router.back()" type="button"
|
</style>
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="mx-2 fa fa-file-import"></i>
|
|
||||||
حواله ورود به انبار
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<button @click="autofill()" class="btn btn-sm btn-outline-primary">
|
|
||||||
<i class="fa fa-list-check me-2"></i>
|
|
||||||
تکمیل خودکار
|
|
||||||
</button>
|
|
||||||
<button :disabled="this.loading" @click="submit()" type="button" class="mx-2 btn btn-sm btn-success">
|
|
||||||
<i class="fa fa-save me-2"></i>
|
|
||||||
ثبت حواله ورود
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">تاریخ</label>
|
|
||||||
<date-picker class="" v-model="this.ticket.date" format="jYYYY/jMM/jDD" display-format="jYYYY/jMM/jDD"
|
|
||||||
:min="year.start" :max="year.end" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">انبار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.store.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">خریدار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.person.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<label class="form-label">شرح</label>
|
|
||||||
<input v-model="this.ticket.des" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">حمل و نقل</label>
|
|
||||||
<input v-model="this.ticket.transfer" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">تحویل</label>
|
|
||||||
<input v-model="this.ticket.receiver" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">روش تحویل</label>
|
|
||||||
<select class="form-select" v-model="ticket.transferType">
|
|
||||||
<option v-for="transferType in transferTypes" :value="transferType">{{ transferType.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">شماره پیگیری</label>
|
|
||||||
<input v-model="this.ticket.referral" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<EasyDataTable table-class-name="customize-table" multi-sort show-index alternating :headers="headers"
|
|
||||||
:items="items" theme-color="#1d90ff" header-text-direction="center" body-text-direction="center"
|
|
||||||
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از"
|
|
||||||
:loading="this.loading">
|
|
||||||
<template #item-commdityCount="{ index, commdityCount, ticketCount }">
|
|
||||||
<input @blur="(event) => { if (this.items[index - 1].ticketCount === '') { this.items[index - 1].ticketCount = 0 } }"
|
|
||||||
@keypress="isNumber($event)" class="form-control form-control-sm" type="number" min="0"
|
|
||||||
:max="this.items[index - 1].remain" v-model="this.items[index - 1].ticketCount" />
|
|
||||||
</template>
|
|
||||||
<template #item-des="{ index, des }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].des" />
|
|
||||||
</template>
|
|
||||||
<template #item-referal="{ index }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].referral" />
|
|
||||||
</template>
|
|
||||||
</EasyDataTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,257 +1,455 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import {defineComponent, ref} from 'vue'
|
<v-toolbar color="toolbar" title="حواله انبار جدید">
|
||||||
import recList from "../../component/recList.vue";
|
<template v-slot:prepend>
|
||||||
import axios from "axios";
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
import Swal from "sweetalert2";
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
<v-tooltip text="ثبت" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
|
color="primary"
|
||||||
|
icon="mdi-content-save"
|
||||||
|
@click="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
export default defineComponent({
|
<v-container>
|
||||||
name: "modalNew",
|
<v-row>
|
||||||
components: {},
|
<!-- ستون سمت راست - انتخاب انبار -->
|
||||||
watch:{
|
<v-col cols="12" md="4">
|
||||||
'item.type'(newValue,oldValue) {
|
<v-card variant="outlined" class="h-100">
|
||||||
if (newValue == 'sell') { this.$data.item.title = 'فروش'; this.$data.item.removeBeforeTicketsEnable = true; }
|
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||||
else if (newValue == 'buy') { this.$data.item.title = 'خرید'; this.$data.item.removeBeforeTicketsEnable = true; }
|
<v-icon start>mdi-warehouse</v-icon>
|
||||||
else if (newValue == 'rfbuy') { this.$data.item.title = 'برگشت از خرید'; this.$data.item.removeBeforeTicketsEnable = true; }
|
انتخاب انبار
|
||||||
else if (newValue == 'rfsell') { this.$data.item.title = 'برگشت از فروش'; this.$data.item.removeBeforeTicketsEnable = true; }
|
</v-card-title>
|
||||||
else if (newValue == 'wastage') {
|
<v-card-text>
|
||||||
this.$data.item.title = 'ضایعات';
|
<v-select
|
||||||
this.$data.item.removeBeforeTicketsEnable = false;
|
v-model="item.storeroom"
|
||||||
this.$data.item.removeBeforeTickets = false;
|
:items="storerooms"
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
label="انبار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:loading="loading"
|
||||||
|
return-object
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template v-slot:no-data>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>
|
||||||
|
نتیجهای یافت نشد!
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- ستون وسط - نوع حواله -->
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||||
|
<v-icon start>mdi-file-document-edit</v-icon>
|
||||||
|
نوع حواله
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-radio-group v-model="item.type" class="mt-0">
|
||||||
|
<v-radio value="sell" color="success">
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="success" class="ml-2">mdi-cart-arrow-down</v-icon>
|
||||||
|
<span>حواله برای فاکتور فروش</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-radio>
|
||||||
|
<v-radio value="buy" color="primary">
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="primary" class="ml-2">mdi-cart-arrow-up</v-icon>
|
||||||
|
<span>حواله برای فاکتور خرید</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-radio>
|
||||||
|
<v-radio value="rfbuy" color="warning">
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="warning" class="ml-2">mdi-cart-remove</v-icon>
|
||||||
|
<span>حواله برای فاکتور برگشت از خرید</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-radio>
|
||||||
|
<v-radio value="rfsell" color="error">
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="error" class="ml-2">mdi-cart-remove</v-icon>
|
||||||
|
<span>حواله برای فاکتور برگشت از فروش</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-radio>
|
||||||
|
</v-radio-group>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- ستون سمت چپ - انتخاب فاکتور -->
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||||
|
<v-icon start>mdi-file-document</v-icon>
|
||||||
|
انتخاب فاکتور
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-select
|
||||||
|
v-if="item.type === 'buy'"
|
||||||
|
v-model="item.docBuy"
|
||||||
|
:items="buys"
|
||||||
|
item-title="des"
|
||||||
|
item-value="code"
|
||||||
|
label="فاکتور خرید"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:loading="loading"
|
||||||
|
return-object
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template v-slot:no-data>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>
|
||||||
|
نتیجهای یافت نشد!
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-if="item.type === 'sell'"
|
||||||
|
v-model="item.docSell"
|
||||||
|
:items="sells"
|
||||||
|
item-title="des"
|
||||||
|
item-value="code"
|
||||||
|
label="فاکتور فروش"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:loading="loading"
|
||||||
|
return-object
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template v-slot:no-data>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>
|
||||||
|
نتیجهای یافت نشد!
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-if="item.type === 'rfsell'"
|
||||||
|
v-model="item.docRfsell"
|
||||||
|
:items="rfsells"
|
||||||
|
item-title="des"
|
||||||
|
item-value="code"
|
||||||
|
label="فاکتور برگشت از فروش"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:loading="loading"
|
||||||
|
return-object
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template v-slot:no-data>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>
|
||||||
|
نتیجهای یافت نشد!
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-if="item.type === 'rfbuy'"
|
||||||
|
v-model="item.docRfbuy"
|
||||||
|
:items="rfbuys"
|
||||||
|
item-title="des"
|
||||||
|
item-value="code"
|
||||||
|
label="فاکتور برگشت از خرید"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:loading="loading"
|
||||||
|
return-object
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template v-slot:no-data>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>
|
||||||
|
نتیجهای یافت نشد!
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
|
||||||
|
<v-checkbox
|
||||||
|
v-model="item.removeBeforeTickets"
|
||||||
|
:disabled="!item.removeBeforeTicketsEnable"
|
||||||
|
label="حذف حوالههای انباری که قبلا برای این فاکتور صادر شده است"
|
||||||
|
color="warning"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
interface Storeroom {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
manager: string;
|
||||||
}
|
}
|
||||||
else if (newValue == 'used') {
|
|
||||||
this.$data.item.title = 'مصرف مستقیم';
|
interface Document {
|
||||||
this.$data.item.removeBeforeTicketsEnable = false;
|
code: string;
|
||||||
this.$data.item.removeBeforeTickets = false;
|
des: string;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
interface Item {
|
||||||
data: () => {
|
storeroom: Storeroom | null;
|
||||||
return {
|
type: 'sell' | 'buy' | 'rfbuy' | 'rfsell' | 'wastage' | 'used';
|
||||||
loading: ref(false),
|
title: string;
|
||||||
storerooms: [],
|
docSell: Document | null;
|
||||||
item: {
|
docBuy: Document | null;
|
||||||
|
docRfsell: Document | null;
|
||||||
|
docRfbuy: Document | null;
|
||||||
|
removeBeforeTickets: boolean;
|
||||||
|
removeBeforeTicketsEnable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const storerooms = ref<Storeroom[]>([])
|
||||||
|
const buys = ref<Document[]>([])
|
||||||
|
const sells = ref<Document[]>([])
|
||||||
|
const rfsells = ref<Document[]>([])
|
||||||
|
const rfbuys = ref<Document[]>([])
|
||||||
|
|
||||||
|
const item = ref<Item>({
|
||||||
storeroom: null,
|
storeroom: null,
|
||||||
type: 'sell',
|
type: 'sell',
|
||||||
title: 'فروش',
|
title: 'فروش',
|
||||||
docSell: null,
|
docSell: null,
|
||||||
docBuy: null,
|
docBuy: null,
|
||||||
|
docRfsell: null,
|
||||||
|
docRfbuy: null,
|
||||||
removeBeforeTickets: true,
|
removeBeforeTickets: true,
|
||||||
removeBeforeTicketsEnable: true
|
removeBeforeTicketsEnable: true
|
||||||
},
|
|
||||||
buys: [],
|
|
||||||
sells: [],
|
|
||||||
rfsells: [],
|
|
||||||
rfbuys: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadData() {
|
|
||||||
this.loading = true;
|
|
||||||
axios.post('/api/storeroom/list')
|
|
||||||
.then((response) => {
|
|
||||||
this.storerooms = response.data.data;
|
|
||||||
this.storerooms.forEach((element) => {
|
|
||||||
element.name = element.name + ' انباردار : ' + element.manager
|
|
||||||
});
|
|
||||||
if (this.storerooms.length != 0) {
|
|
||||||
this.item.storeroom = this.storerooms[0];
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
axios.post('/api/storeroom/docs/get').then((response) => {
|
|
||||||
this.buys = response.data.buys;
|
|
||||||
this.sells = response.data.sells;
|
|
||||||
this.rfsells = response.data.rfsells;
|
|
||||||
this.rfbuys = response.data.rfbuys;
|
|
||||||
})
|
})
|
||||||
},
|
|
||||||
submit() {
|
const snackbar = ref({
|
||||||
this.loading = true;
|
show: false,
|
||||||
if (this.item.storeroom == null) {
|
message: '',
|
||||||
Swal.fire({
|
color: 'primary' as 'primary' | 'error' | 'success' | 'warning'
|
||||||
text: 'انبار انتخاب نشده است.',
|
})
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
// Watchers
|
||||||
}).then((res) => {
|
watch(() => item.value.type, (newValue) => {
|
||||||
this.loading = false;
|
if (newValue === 'sell') {
|
||||||
});
|
item.value.title = 'فروش'
|
||||||
}
|
item.value.removeBeforeTicketsEnable = true
|
||||||
else if (this.item.type == 'sell' && this.item.docSell == null) {
|
} else if (newValue === 'buy') {
|
||||||
Swal.fire({
|
item.value.title = 'خرید'
|
||||||
text: 'فاکتور فروش انتخاب نشده است.',
|
item.value.removeBeforeTicketsEnable = true
|
||||||
icon: 'error',
|
} else if (newValue === 'rfbuy') {
|
||||||
confirmButtonText: 'قبول'
|
item.value.title = 'برگشت از خرید'
|
||||||
}).then((res) => {
|
item.value.removeBeforeTicketsEnable = true
|
||||||
this.loading = false;
|
} else if (newValue === 'rfsell') {
|
||||||
});
|
item.value.title = 'برگشت از فروش'
|
||||||
}
|
item.value.removeBeforeTicketsEnable = true
|
||||||
else if (this.item.type == 'buy' && this.item.docBuy == null) {
|
} else if (newValue === 'wastage') {
|
||||||
Swal.fire({
|
item.value.title = 'ضایعات'
|
||||||
text: 'فاکتور خرید انتخاب نشده است.',
|
item.value.removeBeforeTicketsEnable = false
|
||||||
icon: 'error',
|
item.value.removeBeforeTickets = false
|
||||||
confirmButtonText: 'قبول'
|
} else if (newValue === 'used') {
|
||||||
}).then((res) => {
|
item.value.title = 'مصرف مستقیم'
|
||||||
this.loading = false;
|
item.value.removeBeforeTicketsEnable = false
|
||||||
});
|
item.value.removeBeforeTickets = false
|
||||||
}
|
|
||||||
else if (this.item.type == 'rfbuy' && this.item.docRfbuy == null) {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'فاکتور برگشت از خرید انتخاب نشده است.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((res) => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (this.item.type == 'rfsell' && this.item.docRfsell == null) {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'فاکتور برگشت از خرید انتخاب نشده است.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((res) => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//going to save storeroom ticket
|
|
||||||
if (this.item.type == 'sell') {
|
|
||||||
this.$router.push({ name: 'storeroom_new_ticket_sell', params: { doc: this.item.docSell.code, storeID: this.item.storeroom.id } })
|
|
||||||
}
|
|
||||||
else if (this.item.type == 'buy') {
|
|
||||||
this.$router.push({ name: 'storeroom_new_ticket_buy', params: { doc: this.item.docBuy.code, storeID: this.item.storeroom.id } })
|
|
||||||
}
|
|
||||||
else if (this.item.type == 'rfbuy') {
|
|
||||||
this.$router.push({ name: 'storeroom_new_ticket_rfbuy', params: { doc: this.item.docRfbuy.code, storeID: this.item.storeroom.id } })
|
|
||||||
}
|
|
||||||
else if (this.item.type == 'rfsell') {
|
|
||||||
this.$router.push({ name: 'storeroom_new_ticket_rfsell', params: { doc: this.item.docRfsell.code, storeID: this.item.storeroom.id } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeMount() {
|
|
||||||
this.loadData();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [storeroomsResponse, docsResponse] = await Promise.all([
|
||||||
|
axios.post('/api/storeroom/list'),
|
||||||
|
axios.post('/api/storeroom/docs/get')
|
||||||
|
])
|
||||||
|
|
||||||
|
storerooms.value = storeroomsResponse.data.data.map((element: Storeroom) => ({
|
||||||
|
...element,
|
||||||
|
name: `${element.name} انباردار : ${element.manager}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (storerooms.value.length > 0) {
|
||||||
|
item.value.storeroom = storerooms.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
buys.value = docsResponse.data.buys
|
||||||
|
sells.value = docsResponse.data.sells
|
||||||
|
rfsells.value = docsResponse.data.rfsells
|
||||||
|
rfbuys.value = docsResponse.data.rfbuys
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در بارگذاری دادهها',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
if (!item.value.storeroom) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'انبار انتخاب نشده است',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود فاکتور بر اساس نوع حواله
|
||||||
|
let selectedDoc: Document | null = null
|
||||||
|
switch (item.value.type) {
|
||||||
|
case 'sell':
|
||||||
|
selectedDoc = item.value.docSell
|
||||||
|
if (!selectedDoc) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'فاکتور فروش انتخاب نشده است',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'buy':
|
||||||
|
selectedDoc = item.value.docBuy
|
||||||
|
if (!selectedDoc) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'فاکتور خرید انتخاب نشده است',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'rfbuy':
|
||||||
|
selectedDoc = item.value.docRfbuy
|
||||||
|
if (!selectedDoc) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'فاکتور برگشت از خرید انتخاب نشده است',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'rfsell':
|
||||||
|
selectedDoc = item.value.docRfsell
|
||||||
|
if (!selectedDoc) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'فاکتور برگشت از فروش انتخاب نشده است',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'نوع حواله نامعتبر است',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to appropriate route
|
||||||
|
const routes = {
|
||||||
|
sell: { name: 'storeroom_new_ticket_sell', params: { doc: selectedDoc.code, storeID: item.value.storeroom.id } },
|
||||||
|
buy: { name: 'storeroom_new_ticket_buy', params: { doc: selectedDoc.code, storeID: item.value.storeroom.id } },
|
||||||
|
rfbuy: { name: 'storeroom_new_ticket_rfbuy', params: { doc: selectedDoc.code, storeID: item.value.storeroom.id } },
|
||||||
|
rfsell: { name: 'storeroom_new_ticket_rfsell', params: { doc: selectedDoc.code, storeID: item.value.storeroom.id } }
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type RouteType = keyof typeof routes
|
||||||
|
const routeType = item.value.type as RouteType
|
||||||
|
router.push(routes[routeType])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting form:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در ثبت اطلاعات',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle hooks
|
||||||
|
loadData()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="block block-content-full ">
|
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
|
||||||
<h3 class="block-title text-primary-dark">
|
|
||||||
<button @click="$router.back()" type="button"
|
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="fa fa-file-circle-plus"></i>
|
|
||||||
حواله انبار جدید
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<button :disabled="this.loading" @click="submit()" type="button" class="btn btn-sm btn-alt-primary">
|
|
||||||
<i class="fa fa-save me-2"></i>
|
|
||||||
ثبت
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<div class="row content">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<b class="alert alert-light">نوع حواله انبار را انتخاب کنید </b>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-sm-12 col-md-6 mt-2">
|
|
||||||
<div class="form-control mb-2">
|
|
||||||
<label class="form-label">انبار</label>
|
|
||||||
<v-cob dir="rtl" :options="storerooms" label="name" v-model="item.storeroom">
|
|
||||||
<template #no-options="{ search, searching, loading }">
|
|
||||||
نتیجهای یافت نشد!
|
|
||||||
</template>
|
|
||||||
</v-cob>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-6 mt-2">
|
|
||||||
<b class="mb-3 pb-3">نوع حواله انبار</b>
|
|
||||||
<div class="form-check mt-3">
|
|
||||||
<input v-model="this.item.type" value="sell" class="form-check-input" type="radio">
|
|
||||||
<label class="form-check-label">
|
|
||||||
حواله برای فاکتور فروش
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input v-model="this.item.type" value="buy" class="form-check-input" type="radio">
|
|
||||||
<label class="form-check-label">
|
|
||||||
حواله برای فاکتور خرید
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input v-model="this.item.type" value="rfbuy" class="form-check-input" type="radio">
|
|
||||||
<label class="form-check-label">
|
|
||||||
حواله برای فاکتور برگشت از خرید
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input v-model="this.item.type" value="rfsell" class="form-check-input" type="radio">
|
|
||||||
<label class="form-check-label">
|
|
||||||
حواله برای فاکتور برگشت از فروش
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check d-none">
|
|
||||||
<input v-model="this.item.type" value="wastage" class="form-check-input" type="radio">
|
|
||||||
<label class="form-check-label">
|
|
||||||
ضایعات
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check d-none">
|
|
||||||
<input v-model="this.item.type" value="used" class="form-check-input" type="radio">
|
|
||||||
<label class="form-check-label">
|
|
||||||
مصرف مستقیم
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-12 mt-2">
|
|
||||||
<div class="rounded-2 border border-secondary bg-danger-light p-2">
|
|
||||||
<span>فاکتور {{ item.title }}</span>
|
|
||||||
<v-cob v-if="this.item.type == 'buy'" dir="rtl" :options="buys" label="des" v-model="item.docBuy"
|
|
||||||
class="bg-white">
|
|
||||||
<template #no-options="{ search, searching, loading }">
|
|
||||||
نتیجهای یافت نشد!
|
|
||||||
</template>
|
|
||||||
</v-cob>
|
|
||||||
<v-cob v-if="this.item.type == 'sell'" dir="rtl" :options="sells" label="des" v-model="item.docSell"
|
|
||||||
class="bg-white">
|
|
||||||
<template #no-options="{ search, searching, loading }">
|
|
||||||
نتیجهای یافت نشد!
|
|
||||||
</template>
|
|
||||||
</v-cob>
|
|
||||||
<v-cob v-if="this.item.type == 'rfsell'" dir="rtl" :options="rfsells" label="des"
|
|
||||||
v-model="item.docRfsell" class="bg-white">
|
|
||||||
<template #no-options="{ search, searching, loading }">
|
|
||||||
نتیجهای یافت نشد!
|
|
||||||
</template>
|
|
||||||
</v-cob>
|
|
||||||
<v-cob v-if="this.item.type == 'rfbuy'" dir="rtl" :options="rfbuys" label="des"
|
|
||||||
v-model="item.docRfbuy" class="bg-white">
|
|
||||||
<template #no-options="{ search, searching, loading }">
|
|
||||||
نتیجهای یافت نشد!
|
|
||||||
</template>
|
|
||||||
</v-cob>
|
|
||||||
<div class="form-check mt-2 ms-3">
|
|
||||||
<input :disabled="!item.removeBeforeTicketsEnable" v-model="this.item.removeBeforeTickets" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label" >
|
|
||||||
حذف حوالههای انباری که قبلا برای این فاکتور صادر شده است.
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-card:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-radio :deep(.v-label) {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,21 +1,239 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import { defineComponent } from 'vue'
|
<v-toolbar color="toolbar" title="حواله خروج از انبار">
|
||||||
import axios from "axios";
|
<template v-slot:prepend>
|
||||||
import Loading from "vue-loading-overlay";
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
import 'vue-loading-overlay/dist/css/index.css';
|
<template v-slot:activator="{ props }">
|
||||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
import Swal from "sweetalert2";
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
<v-tooltip text="تکمیل خودکار" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
@click="autofill"
|
||||||
|
variant="text"
|
||||||
|
icon="mdi-auto-fix"
|
||||||
|
class="mx-2"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ثبت حواله خروج" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
|
color="success"
|
||||||
|
icon="mdi-content-save"
|
||||||
|
@click="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
export default defineComponent({
|
<v-container>
|
||||||
name: "rfbuy",
|
<v-row>
|
||||||
components: {
|
<v-col cols="12" md="4">
|
||||||
Loading,
|
<v-text-field
|
||||||
},
|
v-model="ticket.date"
|
||||||
data: () => {
|
label="تاریخ"
|
||||||
return {
|
variant="outlined"
|
||||||
loading: false,
|
density="compact"
|
||||||
doc: {},
|
readonly
|
||||||
ticket: {
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.store.des"
|
||||||
|
label="انبار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.person.des"
|
||||||
|
label="خریدار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.des"
|
||||||
|
label="شرح"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.transfer"
|
||||||
|
label="حملونقل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.receiver"
|
||||||
|
label="تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-select
|
||||||
|
v-model="ticket.transferType"
|
||||||
|
:items="transferTypes"
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
label="روش تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.referral"
|
||||||
|
label="شماره پیگیری"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="items"
|
||||||
|
:loading="loading"
|
||||||
|
class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }"
|
||||||
|
density="compact"
|
||||||
|
>
|
||||||
|
<template v-slot:item.commdityCount="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].ticketCount"
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:min="0"
|
||||||
|
:max="item.remain"
|
||||||
|
@blur="(event) => { if (items[index].ticketCount === '') { items[index].ticketCount = 0 } }"
|
||||||
|
@keypress="isNumber($event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.des="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].des"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.referal="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].referral"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
interface TransferType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
des: string;
|
||||||
|
mobile?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
des: string;
|
||||||
|
name: string;
|
||||||
|
manager: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Commodity {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
commdityCount: number;
|
||||||
|
docCount: number;
|
||||||
|
countBefore: number;
|
||||||
|
remain: number;
|
||||||
|
ticketCount: number;
|
||||||
|
des: string;
|
||||||
|
referral: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
type: string;
|
||||||
|
typeString: string;
|
||||||
|
date: string;
|
||||||
|
des: string;
|
||||||
|
transfer: string;
|
||||||
|
receiver: string;
|
||||||
|
code: string;
|
||||||
|
store: Store;
|
||||||
|
person: Person;
|
||||||
|
transferType: TransferType;
|
||||||
|
referral: string;
|
||||||
|
sms?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Year {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
now: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const doc = ref({})
|
||||||
|
const ticket = ref<Ticket>({
|
||||||
type: 'output',
|
type: 'output',
|
||||||
typeString: 'حواله خروج',
|
typeString: 'حواله خروج',
|
||||||
date: '',
|
date: '',
|
||||||
|
@ -23,227 +241,162 @@ export default defineComponent({
|
||||||
transfer: '',
|
transfer: '',
|
||||||
receiver: '',
|
receiver: '',
|
||||||
code: '',
|
code: '',
|
||||||
store: {},
|
store: {} as Store,
|
||||||
person: {},
|
person: {} as Person,
|
||||||
transferType: {},
|
transferType: {} as TransferType,
|
||||||
referral: ''
|
referral: '',
|
||||||
},
|
sms: false
|
||||||
transferTypes: [],
|
|
||||||
year: {},
|
|
||||||
items: [],
|
|
||||||
headers: [
|
|
||||||
{ text: "کد", value: "commodity.code" },
|
|
||||||
{ text: "کالا", value: "commodity.name", sortable: true },
|
|
||||||
{ text: "واحد", value: "commodity.unit", sortable: true },
|
|
||||||
{ text: "مورد نیاز", value: "docCount" },
|
|
||||||
{ text: "از قبل", value: "countBefore" },
|
|
||||||
{ text: "باقیمانده", value: "remain" },
|
|
||||||
{ text: "تعداد", value: "commdityCount", sortable: true },
|
|
||||||
{ text: "ارجاع", value: "referal", sortable: true },
|
|
||||||
{ text: "توضیحات", value: "des" },
|
|
||||||
],
|
|
||||||
currencyConfig: {
|
|
||||||
masked: false,
|
|
||||||
prefix: '',
|
|
||||||
suffix: '',
|
|
||||||
thousands: ',',
|
|
||||||
decimal: '.',
|
|
||||||
precision: 0,
|
|
||||||
disableNegative: false,
|
|
||||||
disabled: false,
|
|
||||||
min: 0,
|
|
||||||
max: null,
|
|
||||||
allowBlank: false,
|
|
||||||
minimumNumberOfCharacters: 0,
|
|
||||||
shouldRound: true,
|
|
||||||
focusOnRight: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit() {
|
|
||||||
this.loading = true;
|
|
||||||
let errors = [];
|
|
||||||
let rowsWithZeroCount = 0;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
if (element.ticketCount === '') {
|
|
||||||
errors.push('تعداد کالا در ردیف ' + (index + 1) + 'وارد نشده است.');
|
|
||||||
}
|
|
||||||
else if (element.ticketCount === 0) {
|
|
||||||
rowsWithZeroCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//check all values is zero
|
|
||||||
if (rowsWithZeroCount != 0) {
|
|
||||||
errors.push('تعداد تمام کالاها صفر است!');
|
|
||||||
}
|
|
||||||
if (errors.length != 0) {
|
|
||||||
let errorStr = '<ul>';
|
|
||||||
errors.forEach((item) => { errorStr += '<li>' + item + '</li>' })
|
|
||||||
errorStr += '</ul>'
|
|
||||||
Swal.fire({
|
|
||||||
html: errorStr,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//going to save ticket
|
|
||||||
axios.post('/api/storeroom/ticket/insert', {
|
|
||||||
doc: this.doc,
|
|
||||||
ticket: this.ticket,
|
|
||||||
items: this.items
|
|
||||||
}).then((resp) => {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'حواله انبار با موفقیت ثبت شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.$router.push('/acc/storeroom/tickets/list');
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autofill() {
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = this.items[index].remain;
|
|
||||||
this.items[index].des = 'تعداد ' + this.items[index].remain + 'مورد تحویل شد. ';
|
|
||||||
this.items[index].type = 'output';
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const transferTypes = ref<TransferType[]>([])
|
||||||
|
const year = ref<Year>({} as Year)
|
||||||
|
const items = ref<Commodity[]>([])
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ title: "کد", key: "commodity.code" },
|
||||||
|
{ title: "کالا", key: "commodity.name", sortable: true },
|
||||||
|
{ title: "واحد", key: "commodity.unit", sortable: true },
|
||||||
|
{ title: "مورد نیاز", key: "docCount" },
|
||||||
|
{ title: "از قبل", key: "countBefore" },
|
||||||
|
{ title: "باقیمانده", key: "remain" },
|
||||||
|
{ title: "تعداد", key: "commdityCount", sortable: true },
|
||||||
|
{ title: "ارجاع", key: "referal", sortable: true },
|
||||||
|
{ title: "توضیحات", key: "des" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
color: 'primary' as 'primary' | 'error' | 'success' | 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const submit = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const errors: string[] = []
|
||||||
|
let rowsWithZeroCount = 0
|
||||||
|
let totalCount = 0
|
||||||
|
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
if (element.ticketCount === 0) {
|
||||||
|
rowsWithZeroCount++
|
||||||
|
} else if (element.ticketCount === undefined || element.ticketCount === null) {
|
||||||
|
errors.push(`تعداد کالا در ردیف ${index + 1} وارد نشده است.`)
|
||||||
|
} else {
|
||||||
|
totalCount += element.ticketCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
errors.push('تعداد تمام کالاها صفر است!')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length !== 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: errors.join('\n'),
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||||
|
doc: doc.value,
|
||||||
|
ticket: {
|
||||||
|
...ticket.value,
|
||||||
|
senderTel: ticket.value.person.mobile || ''
|
||||||
},
|
},
|
||||||
isNumber(evt: KeyboardEvent): void {
|
items: items.value
|
||||||
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
})
|
||||||
const keyPressed: string = evt.key;
|
|
||||||
|
if (response.data.result === 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||||
|
color: 'success'
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acc/storeroom/tickets/list')
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting form:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در ثبت اطلاعات',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const autofill = () => {
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
const remain = Math.max(0, items.value[index].remain)
|
||||||
|
items.value[index].ticketCount = remain
|
||||||
|
items.value[index].des = remain > 0 ? `تعداد ${remain} مورد تحویل شد.` : ''
|
||||||
|
items.value[index].type = 'output'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNumber = (evt: KeyboardEvent): void => {
|
||||||
|
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||||
|
const keyPressed: string = evt.key
|
||||||
if (!keysAllowed.includes(keyPressed)) {
|
if (!keysAllowed.includes(keyPressed)) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
loadData() {
|
|
||||||
axios.post('/api/storeroom/doc/get/info/' + this.$route.params.doc).then((res) => {
|
|
||||||
this.doc = res.data;
|
|
||||||
this.ticket.person = res.data.person;
|
|
||||||
this.ticket.des = 'حواله خروج از انبار برای فاکتور برگشت از خرید شماره # ' + this.doc.code;
|
|
||||||
this.items = res.data.commodities;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = 0;
|
|
||||||
this.items[index].docCount = element.commdityCount;
|
|
||||||
this.items[index].des = '';
|
|
||||||
this.items[index].type = 'output';
|
|
||||||
})
|
|
||||||
});
|
|
||||||
axios.post('/api/storeroom/info/' + this.$route.params.storeID).then((res) => {
|
|
||||||
this.ticket.store = res.data;
|
|
||||||
this.ticket.store.des = this.ticket.store.name + ' انباردار : ' + this.ticket.store.manager
|
|
||||||
});
|
|
||||||
//load year
|
|
||||||
axios.post('/api/year/get').then((response) => {
|
|
||||||
this.year = response.data;
|
|
||||||
this.ticket.date = response.data.now;
|
|
||||||
})
|
|
||||||
//load transfer types
|
|
||||||
axios.post('/api/storeroom/transfertype/list').then((response) => {
|
|
||||||
this.transferTypes = response.data;
|
|
||||||
this.ticket.transferType = response.data[0];
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.loadData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [docResponse, storeResponse, yearResponse, transferTypesResponse] = await Promise.all([
|
||||||
|
axios.post(`/api/storeroom/doc/get/info/${router.currentRoute.value.params.doc}`),
|
||||||
|
axios.post(`/api/storeroom/info/${router.currentRoute.value.params.storeID}`),
|
||||||
|
axios.post('/api/year/get'),
|
||||||
|
axios.post('/api/storeroom/transfertype/list')
|
||||||
|
])
|
||||||
|
|
||||||
|
doc.value = docResponse.data
|
||||||
|
ticket.value.person = docResponse.data.person
|
||||||
|
ticket.value.des = `حواله خروج از انبار برای فاکتور برگشت از خرید شماره # ${docResponse.data.code}`
|
||||||
|
items.value = docResponse.data.commodities.map((element: Commodity) => ({
|
||||||
|
...element,
|
||||||
|
ticketCount: 0,
|
||||||
|
docCount: element.commdityCount,
|
||||||
|
des: '',
|
||||||
|
type: 'output'
|
||||||
|
}))
|
||||||
|
|
||||||
|
ticket.value.store = storeResponse.data
|
||||||
|
ticket.value.store.des = `${storeResponse.data.name} انباردار : ${storeResponse.data.manager}`
|
||||||
|
|
||||||
|
year.value = yearResponse.data
|
||||||
|
ticket.value.date = yearResponse.data.now
|
||||||
|
|
||||||
|
transferTypes.value = transferTypesResponse.data
|
||||||
|
ticket.value.transferType = transferTypesResponse.data[0]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در بارگذاری دادهها',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<style scoped>
|
||||||
<div class="block block-content-full ">
|
.v-data-table {
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
border-radius: 8px;
|
||||||
<h3 class="block-title text-primary-dark">
|
}
|
||||||
<button @click="$router.back()" type="button"
|
</style>
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="mx-2 fa fa-file-export"></i>
|
|
||||||
حواله خروج از انبار
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<button @click="autofill()" class="btn btn-sm btn-outline-primary">
|
|
||||||
<i class="fa fa-list-check me-2"></i>
|
|
||||||
تکمیل خودکار
|
|
||||||
</button>
|
|
||||||
<button :disabled="this.loading" @click="submit()" type="button" class="mx-2 btn btn-sm btn-success">
|
|
||||||
<i class="fa fa-save me-2"></i>
|
|
||||||
ثبت حواله خروج
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">تاریخ</label>
|
|
||||||
<date-picker class="" v-model="this.ticket.date" format="jYYYY/jMM/jDD" display-format="jYYYY/jMM/jDD"
|
|
||||||
:min="year.start" :max="year.end" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">انبار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.store.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">خریدار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.person.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<label class="form-label">شرح</label>
|
|
||||||
<input v-model="this.ticket.des" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">حمل و نقل</label>
|
|
||||||
<input v-model="this.ticket.transfer" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">تحویل</label>
|
|
||||||
<input v-model="this.ticket.receiver" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">روش تحویل</label>
|
|
||||||
<select class="form-select" v-model="ticket.transferType">
|
|
||||||
<option v-for="transferType in transferTypes" :value="transferType">{{ transferType.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">شماره پیگیری</label>
|
|
||||||
<input v-model="this.ticket.referral" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<EasyDataTable table-class-name="customize-table" multi-sort show-index alternating :headers="headers"
|
|
||||||
:items="items" theme-color="#1d90ff" header-text-direction="center" body-text-direction="center"
|
|
||||||
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از"
|
|
||||||
:loading="this.loading">
|
|
||||||
<template #item-commdityCount="{ index, commdityCount, ticketCount }">
|
|
||||||
<input @blur="(event) => { if (this.items[index - 1].ticketCount === '') { this.items[index - 1].ticketCount = 0 } }"
|
|
||||||
@keypress="isNumber($event)" class="form-control form-control-sm" type="number" min="0"
|
|
||||||
:max="this.items[index - 1].remain" v-model="this.items[index - 1].ticketCount" />
|
|
||||||
</template>
|
|
||||||
<template #item-des="{ index, des }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].des" />
|
|
||||||
</template>
|
|
||||||
<template #item-referal="{ index }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].referral" />
|
|
||||||
</template>
|
|
||||||
</EasyDataTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,249 +1,401 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import { defineComponent } from 'vue'
|
<v-toolbar color="toolbar" title="حواله خروج از انبار">
|
||||||
import axios from "axios";
|
<template v-slot:prepend>
|
||||||
import Loading from "vue-loading-overlay";
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
import 'vue-loading-overlay/dist/css/index.css';
|
<template v-slot:activator="{ props }">
|
||||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
<v-tooltip text="تکمیل خودکار" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
@click="autofill"
|
||||||
|
variant="text"
|
||||||
|
icon="mdi-auto-fix"
|
||||||
|
class="mx-2"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ثبت حواله خروج" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
|
color="success"
|
||||||
|
icon="mdi-content-save"
|
||||||
|
@click="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
import Swal from "sweetalert2";
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.date"
|
||||||
|
label="تاریخ"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.store.des"
|
||||||
|
label="انبار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.person.des"
|
||||||
|
label="فروشنده"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
export default defineComponent({
|
<v-row>
|
||||||
name: "rfsell",
|
<v-col cols="12">
|
||||||
components: {
|
<v-text-field
|
||||||
Loading,
|
v-model="ticket.des"
|
||||||
},
|
label="شرح"
|
||||||
data: () => {
|
variant="outlined"
|
||||||
return {
|
density="compact"
|
||||||
loading: false,
|
/>
|
||||||
doc: {},
|
</v-col>
|
||||||
ticket: {
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.transfer"
|
||||||
|
label="حملونقل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.receiver"
|
||||||
|
label="تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-select
|
||||||
|
v-model="ticket.transferType"
|
||||||
|
:items="transferTypes"
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
label="روش تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.referral"
|
||||||
|
label="شماره پیگیری"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="items"
|
||||||
|
:loading="loading"
|
||||||
|
class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }"
|
||||||
|
density="compact"
|
||||||
|
>
|
||||||
|
<template v-slot:item.commdityCount="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].ticketCount"
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:min="0"
|
||||||
|
:max="item.remain"
|
||||||
|
@blur="(event: Event) => { if (items[index].ticketCount === '') { items[index].ticketCount = 0 } }"
|
||||||
|
@keypress="isNumber($event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.des="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].des"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.referal="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].referral"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
interface TransferType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
des: string;
|
||||||
|
mobile?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
des: string;
|
||||||
|
name: string;
|
||||||
|
manager: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Commodity {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
commdityCount: number;
|
||||||
|
docCount: number;
|
||||||
|
countBefore: number;
|
||||||
|
remain: number;
|
||||||
|
ticketCount: number;
|
||||||
|
des: string;
|
||||||
|
referral: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
type: string;
|
||||||
|
typeString: string;
|
||||||
|
date: string;
|
||||||
|
des: string;
|
||||||
|
transfer: string;
|
||||||
|
receiver: string;
|
||||||
|
code: string;
|
||||||
|
store: Store;
|
||||||
|
person: Person;
|
||||||
|
transferType: TransferType;
|
||||||
|
referral: string;
|
||||||
|
sms?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Year {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
now: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const doc = ref({})
|
||||||
|
const ticket = ref<Ticket>({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
typeString: 'حواله ورود',
|
typeString: 'حواله ورود از انبار',
|
||||||
date: '',
|
date: '',
|
||||||
des: '',
|
des: '',
|
||||||
transfer: '',
|
transfer: '',
|
||||||
receiver: '',
|
receiver: '',
|
||||||
code: '',
|
code: '',
|
||||||
store: {},
|
store: {} as Store,
|
||||||
person: {},
|
person: {} as Person,
|
||||||
transferType: {},
|
transferType: {} as TransferType,
|
||||||
referral: ''
|
referral: '',
|
||||||
},
|
sms: false
|
||||||
transferTypes: [],
|
|
||||||
year: {},
|
|
||||||
items: [],
|
|
||||||
headers: [
|
|
||||||
{ text: "کد", value: "commodity.code" },
|
|
||||||
{ text: "کالا", value: "commodity.name", sortable: true },
|
|
||||||
{ text: "واحد", value: "commodity.unit", sortable: true },
|
|
||||||
{ text: "مورد نیاز", value: "docCount" },
|
|
||||||
{ text: "از قبل", value: "countBefore" },
|
|
||||||
{ text: "باقیمانده", value: "remain" },
|
|
||||||
{ text: "تعداد", value: "commdityCount", sortable: true },
|
|
||||||
{ text: "ارجاع", value: "referal", sortable: true },
|
|
||||||
{ text: "توضیحات", value: "des" },
|
|
||||||
],
|
|
||||||
currencyConfig: {
|
|
||||||
masked: false,
|
|
||||||
prefix: '',
|
|
||||||
suffix: '',
|
|
||||||
thousands: ',',
|
|
||||||
decimal: '.',
|
|
||||||
precision: 0,
|
|
||||||
disableNegative: false,
|
|
||||||
disabled: false,
|
|
||||||
min: 0,
|
|
||||||
max: null,
|
|
||||||
allowBlank: false,
|
|
||||||
minimumNumberOfCharacters: 0,
|
|
||||||
shouldRound: true,
|
|
||||||
focusOnRight: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit() {
|
|
||||||
this.loading = true;
|
|
||||||
let errors = [];
|
|
||||||
let rowsWithZeroCount = 0;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
if (element.ticketCount === '') {
|
|
||||||
errors.push('تعداد کالا در ردیف ' + (index + 1) + 'وارد نشده است.');
|
|
||||||
}
|
|
||||||
else if (element.ticketCount === 0 && element.remain != 0) {
|
|
||||||
rowsWithZeroCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//check all values is zero
|
|
||||||
if (rowsWithZeroCount != 0) {
|
|
||||||
errors.push('تعداد تمام کالاها صفر است!');
|
|
||||||
}
|
|
||||||
if (errors.length != 0) {
|
|
||||||
let errorStr = '<ul>';
|
|
||||||
errors.forEach((item) => { errorStr += '<li>' + item + '</li>' })
|
|
||||||
errorStr += '</ul>'
|
|
||||||
Swal.fire({
|
|
||||||
html: errorStr,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//going to save ticket
|
|
||||||
axios.post('/api/storeroom/ticket/insert', {
|
|
||||||
doc: this.doc,
|
|
||||||
ticket: this.ticket,
|
|
||||||
items: this.items
|
|
||||||
}).then((resp) => {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'حواله انبار با موفقیت ثبت شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.$router.push('/acc/storeroom/tickets/list');
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autofill() {
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = this.items[index].docCount;
|
|
||||||
this.items[index].des = 'تعداد ' + this.items[index].ticketCount + 'مورد تحویل شد. ';
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const transferTypes = ref<TransferType[]>([])
|
||||||
|
const year = ref<Year>({} as Year)
|
||||||
|
const items = ref<Commodity[]>([])
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ title: "کد", key: "commodity.code" },
|
||||||
|
{ title: "کالا", key: "commodity.name", sortable: true },
|
||||||
|
{ title: "واحد", key: "commodity.unit", sortable: true },
|
||||||
|
{ title: "مورد نیاز", key: "docCount" },
|
||||||
|
{ title: "از قبل", key: "countBefore" },
|
||||||
|
{ title: "باقیمانده", key: "remain" },
|
||||||
|
{ title: "تعداد", key: "commdityCount", sortable: true },
|
||||||
|
{ title: "ارجاع", key: "referal", sortable: true },
|
||||||
|
{ title: "توضیحات", key: "des" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
color: 'primary' as 'primary' | 'error' | 'success' | 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const submit = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const errors: string[] = []
|
||||||
|
let rowsWithZeroCount = 0
|
||||||
|
let totalCount = 0
|
||||||
|
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
if (element.ticketCount === 0 && element.remain !== 0) {
|
||||||
|
rowsWithZeroCount++
|
||||||
|
} else if (element.ticketCount === undefined || element.ticketCount === null) {
|
||||||
|
errors.push(`تعداد کالا در ردیف ${index + 1} وارد نشده است.`)
|
||||||
|
} else {
|
||||||
|
totalCount += element.ticketCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (rowsWithZeroCount !== 0) {
|
||||||
|
errors.push('تعداد تمام کالاها صفر است!')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length !== 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: errors.join('\n'),
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||||
|
doc: doc.value,
|
||||||
|
ticket: {
|
||||||
|
...ticket.value,
|
||||||
|
senderTel: ticket.value.person.mobile || ''
|
||||||
},
|
},
|
||||||
isNumber(evt: KeyboardEvent): void {
|
items: items.value
|
||||||
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
})
|
||||||
const keyPressed: string = evt.key;
|
|
||||||
|
if (response.data.result === 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||||
|
color: 'success'
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acc/storeroom/tickets/list')
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting form:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در ثبت اطلاعات',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const autofill = () => {
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
items.value[index].ticketCount = items.value[index].docCount
|
||||||
|
items.value[index].des = `تعداد ${items.value[index].ticketCount} مورد تحویل شد.`
|
||||||
|
items.value[index].type = 'output'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNumber = (evt: KeyboardEvent): void => {
|
||||||
|
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||||
|
const keyPressed: string = evt.key
|
||||||
if (!keysAllowed.includes(keyPressed)) {
|
if (!keysAllowed.includes(keyPressed)) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
loadData() {
|
|
||||||
axios.post('/api/storeroom/doc/get/info/' + this.$route.params.doc).then((res) => {
|
|
||||||
this.doc = res.data;
|
|
||||||
this.ticket.person = res.data.person;
|
|
||||||
this.ticket.des = 'حواله ورود انبار برای فاکتور برگشت از فروش شماره # ' + this.doc.code;
|
|
||||||
this.items = res.data.commodities;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = 0;
|
|
||||||
this.items[index].docCount = element.commdityCount;
|
|
||||||
this.items[index].des = '';
|
|
||||||
this.items[index].type = 'input';
|
|
||||||
})
|
|
||||||
});
|
|
||||||
axios.post('/api/storeroom/info/' + this.$route.params.storeID).then((res) => {
|
|
||||||
this.ticket.store = res.data;
|
|
||||||
this.ticket.store.des = this.ticket.store.name + ' انباردار : ' + this.ticket.store.manager
|
|
||||||
});
|
|
||||||
//load year
|
|
||||||
axios.post('/api/year/get').then((response) => {
|
|
||||||
this.year = response.data;
|
|
||||||
this.ticket.date = response.data.now;
|
|
||||||
})
|
|
||||||
//load transfer types
|
|
||||||
axios.post('/api/storeroom/transfertype/list').then((response) => {
|
|
||||||
this.transferTypes = response.data;
|
|
||||||
this.ticket.transferType = response.data[0];
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.loadData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [docResponse, storeResponse, yearResponse, transferTypesResponse] = await Promise.all([
|
||||||
|
axios.post(`/api/storeroom/doc/get/info/${router.currentRoute.value.params.doc}`),
|
||||||
|
axios.post(`/api/storeroom/info/${router.currentRoute.value.params.storeID}`),
|
||||||
|
axios.post('/api/year/get'),
|
||||||
|
axios.post('/api/storeroom/transfertype/list')
|
||||||
|
])
|
||||||
|
|
||||||
|
doc.value = docResponse.data
|
||||||
|
ticket.value.person = docResponse.data.person
|
||||||
|
ticket.value.des = `حواله خروج از انبار برای فاکتور برگشت از فروش شماره # ${docResponse.data.code}`
|
||||||
|
items.value = docResponse.data.commodities.map((element: Commodity) => ({
|
||||||
|
...element,
|
||||||
|
ticketCount: 0,
|
||||||
|
docCount: element.commdityCount,
|
||||||
|
des: '',
|
||||||
|
type: 'output'
|
||||||
|
}))
|
||||||
|
|
||||||
|
ticket.value.store = storeResponse.data
|
||||||
|
ticket.value.store.des = `${storeResponse.data.name} انباردار : ${storeResponse.data.manager}`
|
||||||
|
|
||||||
|
year.value = yearResponse.data
|
||||||
|
ticket.value.date = yearResponse.data.now
|
||||||
|
|
||||||
|
transferTypes.value = transferTypesResponse.data
|
||||||
|
ticket.value.transferType = transferTypesResponse.data[0]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در بارگذاری دادهها',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<style scoped>
|
||||||
<div class="block block-content-full ">
|
.v-data-table {
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
border-radius: 8px;
|
||||||
<h3 class="block-title text-primary-dark">
|
}
|
||||||
<button @click="$router.back()" type="button"
|
</style>
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="mx-2 fa fa-file-import"></i>
|
|
||||||
حواله ورود به انبار
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<button @click="autofill()" class="btn btn-sm btn-outline-primary">
|
|
||||||
<i class="fa fa-list-check me-2"></i>
|
|
||||||
تکمیل خودکار
|
|
||||||
</button>
|
|
||||||
<button :disabled="this.loading" @click="submit()" type="button" class="mx-2 btn btn-sm btn-success">
|
|
||||||
<i class="fa fa-save me-2"></i>
|
|
||||||
ثبت حواله ورود
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">تاریخ</label>
|
|
||||||
<date-picker class="" v-model="this.ticket.date" format="jYYYY/jMM/jDD" display-format="jYYYY/jMM/jDD"
|
|
||||||
:min="year.start" :max="year.end" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">انبار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.store.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">خریدار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.person.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<label class="form-label">شرح</label>
|
|
||||||
<input v-model="this.ticket.des" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">حمل و نقل</label>
|
|
||||||
<input v-model="this.ticket.transfer" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">تحویل</label>
|
|
||||||
<input v-model="this.ticket.receiver" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">روش تحویل</label>
|
|
||||||
<select class="form-select" v-model="ticket.transferType">
|
|
||||||
<option v-for="transferType in transferTypes" :value="transferType">{{ transferType.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">شماره پیگیری</label>
|
|
||||||
<input v-model="this.ticket.referral" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<EasyDataTable table-class-name="customize-table" multi-sort show-index alternating :headers="headers"
|
|
||||||
:items="items" theme-color="#1d90ff" header-text-direction="center" body-text-direction="center"
|
|
||||||
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از"
|
|
||||||
:loading="this.loading">
|
|
||||||
<template #item-commdityCount="{ index, commdityCount, ticketCount }">
|
|
||||||
<input @blur="(event) => { if (this.items[index - 1].ticketCount === '') { this.items[index - 1].ticketCount = 0 } }"
|
|
||||||
@keypress="isNumber($event)" class="form-control form-control-sm" type="number" min="0"
|
|
||||||
:max="this.items[index - 1].remain" v-model="this.items[index - 1].ticketCount" />
|
|
||||||
</template>
|
|
||||||
<template #item-des="{ index, des }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].des" />
|
|
||||||
</template>
|
|
||||||
<template #item-referal="{ index }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].referral" />
|
|
||||||
</template>
|
|
||||||
</EasyDataTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,22 +1,262 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import { defineComponent } from 'vue'
|
<v-toolbar color="toolbar" title="حواله خروج از انبار">
|
||||||
import axios from "axios";
|
<template v-slot:prepend>
|
||||||
import Loading from "vue-loading-overlay";
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
import 'vue-loading-overlay/dist/css/index.css';
|
<template v-slot:activator="{ props }">
|
||||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
import Swal from "sweetalert2";
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
<v-switch
|
||||||
|
v-if="isPluginActive('accpro')"
|
||||||
|
v-model="ticket.sms"
|
||||||
|
:disabled="!ticket.person.mobile"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
class="mx-2"
|
||||||
|
>
|
||||||
|
<template v-slot:label>
|
||||||
|
<v-icon>mdi-message-text</v-icon>
|
||||||
|
پیامک
|
||||||
|
</template>
|
||||||
|
</v-switch>
|
||||||
|
<v-tooltip text="تکمیل خودکار" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
@click="autofill"
|
||||||
|
variant="text"
|
||||||
|
icon="mdi-auto-fix"
|
||||||
|
class="mx-2"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="ثبت حواله خروج" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
|
color="success"
|
||||||
|
icon="mdi-content-save"
|
||||||
|
@click="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
export default defineComponent({
|
<v-container>
|
||||||
name: "sell",
|
<v-row>
|
||||||
components: {
|
<v-col cols="12" md="4">
|
||||||
Loading,
|
<v-text-field
|
||||||
},
|
v-model="ticket.date"
|
||||||
data: () => {
|
label="تاریخ"
|
||||||
return {
|
variant="outlined"
|
||||||
loading: false,
|
density="compact"
|
||||||
plugins: [],
|
readonly
|
||||||
doc: {},
|
/>
|
||||||
ticket: {
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.store.des"
|
||||||
|
label="انبار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.person.des"
|
||||||
|
label="خریدار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.des"
|
||||||
|
label="شرح"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="2">
|
||||||
|
<v-select
|
||||||
|
v-model="ticket.transferType"
|
||||||
|
:items="transferTypes"
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
label="روش تحویل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.transfer"
|
||||||
|
label="حملونقل/نام باربری"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.receiver"
|
||||||
|
label="تحویل گیرنده"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.referral"
|
||||||
|
label="شماره پیگیری/قبض"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="ticket.senderTel"
|
||||||
|
label="تلفن تحویل دهنده"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="items"
|
||||||
|
:loading="loading"
|
||||||
|
class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }"
|
||||||
|
density="compact"
|
||||||
|
>
|
||||||
|
<template v-slot:item.commdityCount="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].ticketCount"
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:min="0"
|
||||||
|
:max="item.remain"
|
||||||
|
@blur="(event) => { if (items[index].ticketCount === '') { items[index].ticketCount = 0 } }"
|
||||||
|
@keypress="isNumber($event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.des="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].des"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.referal="{ item, index }">
|
||||||
|
<v-text-field
|
||||||
|
v-model="items[index].referral"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
interface TransferType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
des: string;
|
||||||
|
mobile: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
des: string;
|
||||||
|
name: string;
|
||||||
|
manager: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Commodity {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
commdityCount: number;
|
||||||
|
docCount: number;
|
||||||
|
countBefore: number;
|
||||||
|
remain: number;
|
||||||
|
ticketCount: number;
|
||||||
|
des: string;
|
||||||
|
referral: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
type: string;
|
||||||
|
typeString: string;
|
||||||
|
date: string;
|
||||||
|
des: string;
|
||||||
|
transfer: string;
|
||||||
|
receiver: string;
|
||||||
|
code: string;
|
||||||
|
store: Store;
|
||||||
|
person: Person;
|
||||||
|
transferType: TransferType;
|
||||||
|
referral: string;
|
||||||
|
sms: boolean;
|
||||||
|
senderTel: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Year {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
now: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const doc = ref({})
|
||||||
|
const ticket = ref<Ticket>({
|
||||||
type: 'output',
|
type: 'output',
|
||||||
typeString: 'حواله خروج',
|
typeString: 'حواله خروج',
|
||||||
date: '',
|
date: '',
|
||||||
|
@ -24,259 +264,178 @@ export default defineComponent({
|
||||||
transfer: '',
|
transfer: '',
|
||||||
receiver: '',
|
receiver: '',
|
||||||
code: '',
|
code: '',
|
||||||
store: {},
|
store: {} as Store,
|
||||||
person: {},
|
person: {} as Person,
|
||||||
transferType: {},
|
transferType: {} as TransferType,
|
||||||
referral: '',
|
referral: '',
|
||||||
sms: false,
|
sms: false,
|
||||||
senderTel: 0
|
senderTel: 0
|
||||||
},
|
})
|
||||||
transferTypes: [],
|
|
||||||
year: {},
|
const transferTypes = ref<TransferType[]>([])
|
||||||
items: [],
|
const year = ref<Year>({} as Year)
|
||||||
headers: [
|
const items = ref<Commodity[]>([])
|
||||||
{ text: "کد", value: "commodity.code" },
|
const plugins = ref({})
|
||||||
{ text: "کالا", value: "commodity.name", sortable: true },
|
|
||||||
{ text: "واحد", value: "commodity.unit", sortable: true },
|
const headers = [
|
||||||
{ text: "مورد نیاز", value: "docCount" },
|
{ title: "کد", key: "commodity.code" },
|
||||||
{ text: "از قبل", value: "countBefore" },
|
{ title: "کالا", key: "commodity.name", sortable: true },
|
||||||
{ text: "باقیمانده", value: "remain" },
|
{ title: "واحد", key: "commodity.unit", sortable: true },
|
||||||
{ text: "تعداد", value: "commdityCount", sortable: true },
|
{ title: "مورد نیاز", key: "docCount" },
|
||||||
{ text: "ارجاع", value: "referal", sortable: true },
|
{ title: "از قبل", key: "countBefore" },
|
||||||
{ text: "توضیحات", value: "des" },
|
{ title: "باقیمانده", key: "remain" },
|
||||||
],
|
{ title: "تعداد", key: "commdityCount", sortable: true },
|
||||||
currencyConfig: {
|
{ title: "ارجاع", key: "referal", sortable: true },
|
||||||
masked: false,
|
{ title: "توضیحات", key: "des" },
|
||||||
prefix: '',
|
]
|
||||||
suffix: '',
|
|
||||||
thousands: ',',
|
const snackbar = ref({
|
||||||
decimal: '.',
|
show: false,
|
||||||
precision: 0,
|
message: '',
|
||||||
disableNegative: false,
|
color: 'primary' as 'primary' | 'error' | 'success' | 'warning'
|
||||||
disabled: false,
|
})
|
||||||
min: 0,
|
|
||||||
max: null,
|
// Methods
|
||||||
allowBlank: false,
|
const submit = async () => {
|
||||||
minimumNumberOfCharacters: 0,
|
loading.value = true
|
||||||
shouldRound: true,
|
try {
|
||||||
focusOnRight: true,
|
const errors: string[] = []
|
||||||
},
|
let rowsWithZeroCount = 0
|
||||||
|
let totalCount = 0
|
||||||
|
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
if (element.ticketCount === 0) {
|
||||||
|
rowsWithZeroCount++
|
||||||
|
} else if (element.ticketCount === undefined || element.ticketCount === null) {
|
||||||
|
errors.push(`تعداد کالا در ردیف ${index + 1} وارد نشده است.`)
|
||||||
|
} else {
|
||||||
|
totalCount += element.ticketCount
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
methods: {
|
|
||||||
submit() {
|
if (totalCount === 0) {
|
||||||
this.loading = true;
|
errors.push('تعداد تمام کالاها صفر است!')
|
||||||
let errors = [];
|
|
||||||
let rowsWithZeroCount = 0;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
if (element.ticketCount === '') {
|
|
||||||
errors.push('تعداد کالا در ردیف ' + (index + 1) + 'وارد نشده است.');
|
|
||||||
}
|
|
||||||
else if (element.ticketCount === 0) {
|
|
||||||
rowsWithZeroCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//check all values is zero
|
|
||||||
if (rowsWithZeroCount != 0) {
|
|
||||||
errors.push('تعداد تمام کالاها صفر است!');
|
|
||||||
}
|
|
||||||
if (errors.length != 0) {
|
|
||||||
let errorStr = '<ul>';
|
|
||||||
errors.forEach((item) => { errorStr += '<li>' + item + '</li>' })
|
|
||||||
errorStr += '</ul>'
|
|
||||||
Swal.fire({
|
|
||||||
html: errorStr,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//going to save ticket
|
|
||||||
axios.post('/api/storeroom/ticket/insert', {
|
|
||||||
doc: this.doc,
|
|
||||||
ticket: this.ticket,
|
|
||||||
items: this.items
|
|
||||||
}).then((resp) => {
|
|
||||||
if (resp.data.result == 0) {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'حواله انبار با موفقیت ثبت شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.$router.push('/acc/storeroom/tickets/list');
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if(resp.data.result == 2){
|
|
||||||
Swal.fire({
|
|
||||||
text: 'حواله انبار با موفقیت ثبت شد اما به دلیل کمبود اعتبار،پیامک به مشتری ارسال نشد.لطفا برای ارسال پیامک حساب خود را شارژ نمایید..',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((response) => {
|
|
||||||
this.$router.push('/acc/storeroom/tickets/list');
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
if (errors.length !== 0) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: errors.join('\n'),
|
||||||
|
color: 'error'
|
||||||
}
|
}
|
||||||
},
|
return
|
||||||
autofill() {
|
}
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = this.items[index].remain;
|
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||||
this.items[index].des = 'تعداد ' + this.items[index].remain + 'مورد تحویل شد. ';
|
doc: doc.value,
|
||||||
this.items[index].type = 'output';
|
ticket: ticket.value,
|
||||||
|
items: items.value
|
||||||
})
|
})
|
||||||
},
|
|
||||||
isNumber(evt: KeyboardEvent): void {
|
if (response.data.result === 0) {
|
||||||
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
snackbar.value = {
|
||||||
const keyPressed: string = evt.key;
|
show: true,
|
||||||
|
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||||
|
color: 'success'
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acc/storeroom/tickets/list')
|
||||||
|
}, 1000)
|
||||||
|
} else if (response.data.result === 2) {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'حواله انبار با موفقیت ثبت شد اما به دلیل کمبود اعتبار،پیامک به مشتری ارسال نشد.لطفا برای ارسال پیامک حساب خود را شارژ نمایید.',
|
||||||
|
color: 'warning'
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acc/storeroom/tickets/list')
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting form:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در ثبت اطلاعات',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const autofill = () => {
|
||||||
|
items.value.forEach((element, index) => {
|
||||||
|
const remain = Math.max(0, items.value[index].remain)
|
||||||
|
items.value[index].ticketCount = remain
|
||||||
|
items.value[index].des = remain > 0 ? `تعداد ${remain} مورد تحویل شد.` : ''
|
||||||
|
items.value[index].type = 'output'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNumber = (evt: KeyboardEvent): void => {
|
||||||
|
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||||
|
const keyPressed: string = evt.key
|
||||||
if (!keysAllowed.includes(keyPressed)) {
|
if (!keysAllowed.includes(keyPressed)) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
loadData() {
|
|
||||||
axios.post('/api/storeroom/doc/get/info/' + this.$route.params.doc).then((res) => {
|
|
||||||
this.doc = res.data;
|
|
||||||
this.ticket.person = res.data.person;
|
|
||||||
this.ticket.des = 'حواله خروج از انبار برای فاکتور فروش شماره # ' + this.doc.code;
|
|
||||||
this.items = res.data.commodities;
|
|
||||||
this.items.forEach((element, index) => {
|
|
||||||
this.items[index].ticketCount = 0;
|
|
||||||
this.items[index].docCount = element.commdityCount;
|
|
||||||
this.items[index].des = '';
|
|
||||||
this.items[index].type = 'output';
|
|
||||||
})
|
|
||||||
});
|
|
||||||
axios.post('/api/storeroom/info/' + this.$route.params.storeID).then((res) => {
|
|
||||||
this.ticket.store = res.data;
|
|
||||||
this.ticket.store.des = this.ticket.store.name + ' انباردار : ' + this.ticket.store.manager
|
|
||||||
});
|
|
||||||
//load year
|
|
||||||
axios.post('/api/year/get').then((response) => {
|
|
||||||
this.year = response.data;
|
|
||||||
this.ticket.date = response.data.now;
|
|
||||||
})
|
|
||||||
//load transfer types
|
|
||||||
axios.post('/api/storeroom/transfertype/list').then((response) => {
|
|
||||||
this.transferTypes = response.data;
|
|
||||||
this.ticket.transferType = response.data[0];
|
|
||||||
});
|
|
||||||
//load plugins
|
|
||||||
axios.post('/api/plugin/get/actives',).then((response) => {
|
|
||||||
this.plugins = response.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
isPluginActive(plugName) {
|
|
||||||
return this.plugins[plugName] !== undefined;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.loadData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [docResponse, storeResponse, yearResponse, transferTypesResponse, pluginsResponse] = await Promise.all([
|
||||||
|
axios.post(`/api/storeroom/doc/get/info/${router.currentRoute.value.params.doc}`),
|
||||||
|
axios.post(`/api/storeroom/info/${router.currentRoute.value.params.storeID}`),
|
||||||
|
axios.post('/api/year/get'),
|
||||||
|
axios.post('/api/storeroom/transfertype/list'),
|
||||||
|
axios.post('/api/plugin/get/actives')
|
||||||
|
])
|
||||||
|
|
||||||
|
doc.value = docResponse.data
|
||||||
|
ticket.value.person = docResponse.data.person
|
||||||
|
ticket.value.des = `حواله خروج از انبار برای فاکتور فروش شماره # ${docResponse.data.code}`
|
||||||
|
items.value = docResponse.data.commodities.map((element: Commodity) => ({
|
||||||
|
...element,
|
||||||
|
ticketCount: 0,
|
||||||
|
docCount: element.commdityCount,
|
||||||
|
des: '',
|
||||||
|
type: 'output'
|
||||||
|
}))
|
||||||
|
|
||||||
|
ticket.value.store = storeResponse.data
|
||||||
|
ticket.value.store.des = `${storeResponse.data.name} انباردار : ${storeResponse.data.manager}`
|
||||||
|
|
||||||
|
year.value = yearResponse.data
|
||||||
|
ticket.value.date = yearResponse.data.now
|
||||||
|
|
||||||
|
transferTypes.value = transferTypesResponse.data
|
||||||
|
ticket.value.transferType = transferTypesResponse.data[0]
|
||||||
|
|
||||||
|
plugins.value = pluginsResponse.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در بارگذاری دادهها',
|
||||||
|
color: 'error'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPluginActive = (plugName: string) => {
|
||||||
|
return plugins.value[plugName] !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle hooks
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<style scoped>
|
||||||
<div class="block block-content-full ">
|
.v-data-table {
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
border-radius: 8px;
|
||||||
<h3 class="block-title text-primary-dark">
|
}
|
||||||
<button @click="$router.back()" type="button"
|
</style>
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="mx-2 fa fa-file-export"></i>
|
|
||||||
حواله خروج از انبار
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<span v-if="isPluginActive('accpro')" class="form-check form-switch form-check-inline">
|
|
||||||
<input :disabled="this.ticket.person.mobile == ''" v-model="ticket.sms" class="form-check-input"
|
|
||||||
type="checkbox">
|
|
||||||
<label class="form-check-label"> پیامک</label>
|
|
||||||
</span>
|
|
||||||
<button @click="autofill()" class="btn btn-sm btn-outline-primary">
|
|
||||||
<i class="fa fa-list-check me-2"></i>
|
|
||||||
تکمیل خودکار
|
|
||||||
</button>
|
|
||||||
<button :disabled="this.loading" @click="submit()" type="button" class="mx-2 btn btn-sm btn-success">
|
|
||||||
<i class="fa fa-save me-2"></i>
|
|
||||||
ثبت حواله خروج
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">تاریخ</label>
|
|
||||||
<date-picker class="" v-model="this.ticket.date" format="jYYYY/jMM/jDD" display-format="jYYYY/jMM/jDD"
|
|
||||||
:min="year.start" :max="year.end" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">انبار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.store.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<label class="form-label">خریدار</label>
|
|
||||||
<input disabled="disabled" readonly="readonly" v-model="this.ticket.person.des" type="text"
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<label class="form-label">شرح</label>
|
|
||||||
<input v-model="this.ticket.des" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col-sm-12 col-md-2">
|
|
||||||
<label class="form-label">روش تحویل</label>
|
|
||||||
<select class="form-select" v-model="ticket.transferType">
|
|
||||||
<option v-for="transferType in transferTypes" :value="transferType">{{ transferType.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">حملونقل/نام باربری</label>
|
|
||||||
<input v-model="this.ticket.transfer" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-2">
|
|
||||||
<label class="form-label">تحویل گیرنده</label>
|
|
||||||
<input v-model="this.ticket.receiver" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-3">
|
|
||||||
<label class="form-label">شماره پیگیری/قبض</label>
|
|
||||||
<input v-model="this.ticket.referral" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-2">
|
|
||||||
<label class="form-label">تلفن تحویل دهنده</label>
|
|
||||||
<input v-model="this.ticket.senderTel" type="text" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<EasyDataTable table-class-name="customize-table" multi-sort show-index alternating :headers="headers"
|
|
||||||
:items="items" theme-color="#1d90ff" header-text-direction="center" body-text-direction="center"
|
|
||||||
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از"
|
|
||||||
:loading="this.loading">
|
|
||||||
<template #item-commdityCount="{ index, commdityCount, ticketCount }">
|
|
||||||
<input
|
|
||||||
@blur="(event) => { if (this.items[index - 1].ticketCount === '') { this.items[index - 1].ticketCount = 0 } }"
|
|
||||||
@keypress="isNumber($event)" class="form-control form-control-sm" type="number" min="0"
|
|
||||||
:max="this.items[index - 1].remain" v-model="this.items[index - 1].ticketCount" />
|
|
||||||
</template>
|
|
||||||
<template #item-des="{ index, des }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].des" />
|
|
||||||
</template>
|
|
||||||
<template #item-referal="{ index }">
|
|
||||||
<input class="form-control form-control-sm" type="text" v-model="this.items[index - 1].referral" />
|
|
||||||
</template>
|
|
||||||
</EasyDataTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,191 +1,358 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import {defineComponent,ref} from 'vue'
|
<v-toolbar color="toolbar" title="حوالههای انبار">
|
||||||
import axios from "axios";
|
<template v-slot:prepend>
|
||||||
import Swal from "sweetalert2";
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
|
||||||
|
icon="mdi-arrow-right" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer />
|
||||||
|
|
||||||
export default defineComponent({
|
<v-slide-group show-arrows>
|
||||||
name: "ticketList",
|
<v-slide-group-item>
|
||||||
data: ()=>{return {
|
<v-tooltip text="حواله جدید" location="bottom">
|
||||||
printID:'',
|
<template v-slot:activator="{ props }">
|
||||||
loading : ref(false),
|
<v-btn v-bind="props" color="primary" icon="mdi-plus" :to="'/acc/storeroom/new/ticket/type'" />
|
||||||
inputItems:[],
|
</template>
|
||||||
inputSearchValue: '',
|
</v-tooltip>
|
||||||
outputItems:[],
|
</v-slide-group-item>
|
||||||
outputSearchValue: '',
|
|
||||||
headers: [
|
<v-slide-group-item>
|
||||||
{ text: "عملیات", value: "operation", width: "120" },
|
<v-tooltip text="تنظیمات ستونها" location="bottom">
|
||||||
{ text: "شماره", value: "code" },
|
<template v-slot:activator="{ props }">
|
||||||
{ text: "تاریخ", value: "date", sortable: true },
|
<v-btn v-bind="props" icon="mdi-table-cog" color="primary" @click="showColumnDialog = true" />
|
||||||
{ text: "شماره فاکتور", value: "doc.code", sortable: true, width: "100" },
|
</template>
|
||||||
{ text: "شخص", value: "person.nikename", sortable: true, width: "120" },
|
</v-tooltip>
|
||||||
{ text: "توضیحات", value: "des", sortable: true, width: "300" },
|
</v-slide-group-item>
|
||||||
]
|
</v-slide-group>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-tabs v-model="activeTab" color="primary" grow class="mb-3">
|
||||||
|
<v-tab value="output">
|
||||||
|
<v-icon start>mdi-file-export</v-icon>
|
||||||
|
حوالههای خروج
|
||||||
|
</v-tab>
|
||||||
|
<v-tab value="input">
|
||||||
|
<v-icon start>mdi-file-import</v-icon>
|
||||||
|
حوالههای ورود
|
||||||
|
</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
|
||||||
|
<v-window v-model="activeTab">
|
||||||
|
<!-- تب حوالههای خروج -->
|
||||||
|
<v-window-item value="output">
|
||||||
|
<v-text-field v-model="outputSearchValue" prepend-inner-icon="mdi-magnify" label="جستجو" variant="outlined"
|
||||||
|
density="compact" hide-details class="mb-1"></v-text-field>
|
||||||
|
|
||||||
|
<v-data-table :headers="visibleHeaders" :items="outputItems" :search="outputSearchValue" :loading="loading"
|
||||||
|
hover density="compact" class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }">
|
||||||
|
<template v-slot:item="{ item }">
|
||||||
|
<tr>
|
||||||
|
<td v-if="isColumnVisible('operation')" class="text-center">
|
||||||
|
<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/storeroom/ticket/view/' + item.code">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="success">mdi-eye</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>مشاهده</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item @click="deleteTicket('output', item.code)">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="error">mdi-delete</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>حذف</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</td>
|
||||||
|
<td v-if="isColumnVisible('code')" class="text-center">{{ formatNumber(item.code) }}</td>
|
||||||
|
<td v-if="isColumnVisible('date')" class="text-center">{{ item.date }}</td>
|
||||||
|
<td v-if="isColumnVisible('doc.code')" class="text-center">{{ item.doc.code }}</td>
|
||||||
|
<td v-if="isColumnVisible('person.nikename')" class="text-center">{{ item.person.nikename }}</td>
|
||||||
|
<td v-if="isColumnVisible('des')" class="text-center">{{ item.des }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
|
<!-- تب حوالههای ورود -->
|
||||||
|
<v-window-item value="input">
|
||||||
|
<v-text-field v-model="inputSearchValue" prepend-inner-icon="mdi-magnify" label="جستجو" variant="outlined"
|
||||||
|
density="compact" hide-details class="mb-1"></v-text-field>
|
||||||
|
|
||||||
|
<v-data-table :headers="visibleHeaders" :items="inputItems" :search="inputSearchValue" :loading="loading" hover
|
||||||
|
density="compact" class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }">
|
||||||
|
<template v-slot:item="{ item }">
|
||||||
|
<tr>
|
||||||
|
<td v-if="isColumnVisible('operation')" class="text-center">
|
||||||
|
<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/storeroom/ticket/view/' + item.code">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="success">mdi-eye</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>مشاهده</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item @click="deleteTicket('input', item.code)">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="error">mdi-delete</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>حذف</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</td>
|
||||||
|
<td v-if="isColumnVisible('code')" class="text-center">{{ formatNumber(item.code) }}</td>
|
||||||
|
<td v-if="isColumnVisible('date')" class="text-center">{{ item.date }}</td>
|
||||||
|
<td v-if="isColumnVisible('doc.code')" class="text-center">{{ item.doc.code }}</td>
|
||||||
|
<td v-if="isColumnVisible('person.nikename')" class="text-center">{{ item.person.nikename }}</td>
|
||||||
|
<td v-if="isColumnVisible('des')" class="text-center">{{ item.des }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-window-item>
|
||||||
|
</v-window>
|
||||||
|
|
||||||
|
<v-dialog v-model="showColumnDialog" max-width="500">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="toolbar" title="مدیریت ستونها">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon @click="showColumnDialog = false">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col v-for="header in allHeaders" :key="header.key" cols="12" sm="6">
|
||||||
|
<v-checkbox v-model="header.visible" :label="header.title" @change="updateColumnVisibility" hide-details />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-dialog v-model="deleteDialog.show" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h6">
|
||||||
|
تأیید حذف
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
آیا برای حذف حواله مطمئن هستید؟
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" variant="text" @click="deleteDialog.show = false">خیر</v-btn>
|
||||||
|
<v-btn color="error" variant="text" @click="confirmDelete">بله</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000" location="bottom">
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn variant="text" @click="snackbar.show = false">
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
code: string;
|
||||||
|
date: string;
|
||||||
|
doc: {
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
person: {
|
||||||
|
nikename: string;
|
||||||
|
};
|
||||||
|
des: string;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
interface Header {
|
||||||
loadData() {
|
title: string;
|
||||||
axios.post('/api/storeroom/tickets/list/input')
|
key: string;
|
||||||
.then((response) => {
|
align: string;
|
||||||
this.inputItems = response.data;
|
sortable: boolean;
|
||||||
this.loading = false;
|
width: number;
|
||||||
|
visible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const loading = ref(false);
|
||||||
|
const inputItems = ref<Ticket[]>([]);
|
||||||
|
const outputItems = ref<Ticket[]>([]);
|
||||||
|
const inputSearchValue = ref('');
|
||||||
|
const outputSearchValue = ref('');
|
||||||
|
const activeTab = ref('output');
|
||||||
|
const showColumnDialog = ref(false);
|
||||||
|
|
||||||
|
// دیالوگها
|
||||||
|
const deleteDialog = ref({
|
||||||
|
show: false,
|
||||||
|
type: null as 'input' | 'output' | null,
|
||||||
|
code: null as string | null
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
color: 'primary'
|
||||||
|
});
|
||||||
|
|
||||||
|
// تعریف همه ستونها
|
||||||
|
const allHeaders = ref<Header[]>([
|
||||||
|
{ title: "عملیات", key: "operation", align: 'center', sortable: false, width: 100, visible: true },
|
||||||
|
{ title: "شماره", key: "code", align: 'center', sortable: true, width: 100, visible: true },
|
||||||
|
{ title: "تاریخ", key: "date", align: 'center', sortable: true, width: 120, visible: true },
|
||||||
|
{ title: "شماره فاکتور", key: "doc.code", align: 'center', sortable: true, width: 120, visible: true },
|
||||||
|
{ title: "شخص", key: "person.nikename", align: 'center', sortable: true, width: 120, visible: true },
|
||||||
|
{ title: "توضیحات", key: "des", align: 'center', sortable: true, width: 200, visible: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ستونهای قابل نمایش
|
||||||
|
const visibleHeaders = computed(() => {
|
||||||
|
return allHeaders.value.filter((header: Header) => header.visible);
|
||||||
|
});
|
||||||
|
|
||||||
|
// بررسی نمایش ستون
|
||||||
|
const isColumnVisible = (key: string) => {
|
||||||
|
return allHeaders.value.find((header: Header) => header.key === key)?.visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
// کلید ذخیرهسازی در localStorage
|
||||||
|
const LOCAL_STORAGE_KEY = 'hesabix_storeroom_tickets_table_columns';
|
||||||
|
|
||||||
|
// لود تنظیمات ستونها
|
||||||
|
const loadColumnSettings = () => {
|
||||||
|
const savedSettings = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||||
|
if (savedSettings) {
|
||||||
|
const visibleColumns = JSON.parse(savedSettings);
|
||||||
|
allHeaders.value.forEach(header => {
|
||||||
|
header.visible = visibleColumns.includes(header.key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ذخیره تنظیمات ستونها
|
||||||
|
const updateColumnVisibility = () => {
|
||||||
|
const visibleColumns = allHeaders.value
|
||||||
|
.filter(header => header.visible)
|
||||||
|
.map(header => header.key);
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(visibleColumns));
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع فرمتکننده اعداد
|
||||||
|
const formatNumber = (value: string | number) => {
|
||||||
|
if (!value) return '0';
|
||||||
|
return Number(value).toLocaleString('fa-IR');
|
||||||
|
};
|
||||||
|
|
||||||
|
// بارگذاری دادهها
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const [inputResponse, outputResponse] = await Promise.all([
|
||||||
|
axios.post('/api/storeroom/tickets/list/input'),
|
||||||
axios.post('/api/storeroom/tickets/list/output')
|
axios.post('/api/storeroom/tickets/list/output')
|
||||||
.then((response) => {
|
]);
|
||||||
this.outputItems = response.data;
|
inputItems.value = inputResponse.data;
|
||||||
this.loading = false;
|
outputItems.value = outputResponse.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در بارگذاری دادهها: ' + error.message,
|
||||||
|
color: 'error'
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// حذف حواله
|
||||||
|
const deleteTicket = (type: 'input' | 'output', code: string) => {
|
||||||
|
deleteDialog.value = {
|
||||||
|
show: true,
|
||||||
|
type,
|
||||||
|
code
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// تأیید حذف
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!deleteDialog.value?.type || !deleteDialog.value?.code) return;
|
||||||
|
|
||||||
|
const { type, code } = deleteDialog.value;
|
||||||
|
deleteDialog.value.show = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await axios.post('/api/storeroom/ticket/remove/' + code);
|
||||||
|
|
||||||
|
if (type === 'input') {
|
||||||
|
inputItems.value = inputItems.value.filter(item => item.code !== code);
|
||||||
|
} else {
|
||||||
|
outputItems.value = outputItems.value.filter(item => item.code !== code);
|
||||||
|
}
|
||||||
|
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'حواله انبار حذف شد.',
|
||||||
|
color: 'success'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting ticket:', error);
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message: 'خطا در حذف حواله: ' + error.message,
|
||||||
|
color: 'error'
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
deleteDialog.value = {
|
||||||
|
show: false,
|
||||||
|
type: null,
|
||||||
|
code: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// مانت کامپوننت
|
||||||
|
onMounted(() => {
|
||||||
|
loadColumnSettings();
|
||||||
|
loadData();
|
||||||
});
|
});
|
||||||
axios.post('/api/storeroom/tickets/list/input')
|
|
||||||
.then((response) => {
|
|
||||||
this.inputItems = response.data;
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteTicket(type, code) {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'آیا برای حذف این حواله مطمئن هستید؟',
|
|
||||||
icon: 'warning',
|
|
||||||
confirmButtonText: 'قبول',
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: 'انصراف'
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
this.loading = true;
|
|
||||||
axios.post('/api/storeroom/ticket/remove/' + code)
|
|
||||||
.then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
Swal.fire({
|
|
||||||
text: 'حواله انبار حذف شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((result) => {
|
|
||||||
if (type == 'input') {
|
|
||||||
for (let z = 0; z < this.inputItems.length; z++) {
|
|
||||||
if (this.inputItems[z]['code'] == code) {
|
|
||||||
this.inputItems.splice(z, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (type == 'output') {
|
|
||||||
for (let z = 0; z < this.outputItems.length; z++) {
|
|
||||||
if (this.outputItems[z]['code'] == code) {
|
|
||||||
this.outputItems.splice(z, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeMount() {
|
|
||||||
this.loadData();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="block block-content-full ">
|
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
|
||||||
<h3 class="block-title text-primary-dark">
|
|
||||||
<button @click="$router.back()" type="button"
|
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="mx-2 fa fa-folder-tree"></i>
|
|
||||||
حوالههای انبار
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<router-link to="/acc/storeroom/new/ticket/type" class="btn btn-sm btn-primary ms-1">
|
|
||||||
<span class="fa fa-plus fw-bolder"></span>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content p-0">
|
|
||||||
<div class="col-sm-12 col-md-12 m-0 p-0">
|
|
||||||
<ul class="nav nav-pills flex-column flex-sm-row border border-secondary" id="myTab" role="tablist">
|
|
||||||
<button class="flex-sm-fill text-sm-center nav-link rounded-0 active" id="profile-tab" data-bs-toggle="tab"
|
|
||||||
data-bs-target="#profile" type="button" role="tab" aria-controls="profile" aria-selected="true">
|
|
||||||
<i class="fa fa-file-export me-2"></i>
|
|
||||||
حوالههای خروج
|
|
||||||
</button>
|
|
||||||
<button class="flex-sm-fill text-sm-center nav-link rounded-0" id="pays-tab" data-bs-toggle="tab"
|
|
||||||
data-bs-target="#pays" type="button" role="tab" aria-controls="pays" aria-selected="false">
|
|
||||||
<i class="fa fa-file-import me-2"></i>
|
|
||||||
حوالههای ورود
|
|
||||||
</button>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content p-0" id="myTabContent">
|
|
||||||
<div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="profile-tab">
|
|
||||||
<div class="m-1">
|
|
||||||
<div class="input-group input-group-sm">
|
|
||||||
<span class="input-group-text"><i class="fa fa-search"></i></span>
|
|
||||||
<input v-model="outputSearchValue" class="form-control" type="text" placeholder="جست و جو ...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EasyDataTable table-class-name="customize-table" multi-sort show-index alternating
|
|
||||||
:search-value="outputSearchValue" :headers="headers" :items="outputItems" theme-color="#1d90ff"
|
|
||||||
header-text-direction="center" body-text-direction="center" rowsPerPageMessage="تعداد سطر"
|
|
||||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از" :loading="loading">
|
|
||||||
<template #item-operation="{ code }">
|
|
||||||
<div class="dropdown-center">
|
|
||||||
<button aria-expanded="false" aria-haspopup="true" class="btn btn-sm btn-link"
|
|
||||||
data-bs-toggle="dropdown" id="dropdown-align-center-alt-primary" type="button">
|
|
||||||
<i class="fa-solid fa-ellipsis"></i>
|
|
||||||
</button>
|
|
||||||
<div aria-labelledby="dropdown-align-center-outline-primary" class="dropdown-menu dropdown-menu-end"
|
|
||||||
style="">
|
|
||||||
<router-link class="dropdown-item" :to="'/acc/storeroom/ticket/view/' + code">
|
|
||||||
<i class="fa fa-eye text-success pe-2"></i>
|
|
||||||
مشاهده
|
|
||||||
</router-link>
|
|
||||||
<button type="button" @click="deleteTicket('output', code)" class="dropdown-item text-danger">
|
|
||||||
<i class="fa fa-trash pe-2"></i>
|
|
||||||
حذف
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</EasyDataTable>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="pays" role="tabpanel" aria-labelledby="pays-tab">
|
|
||||||
<div class="m-1">
|
|
||||||
<div class="input-group input-group-sm">
|
|
||||||
<span class="input-group-text"><i class="fa fa-search"></i></span>
|
|
||||||
<input v-model="inputSearchValue" class="form-control" type="text" placeholder="جست و جو ...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EasyDataTable table-class-name="customize-table" multi-sort show-index alternating
|
|
||||||
:search-value="inputSearchValue" :headers="headers" :items="inputItems" theme-color="#1d90ff"
|
|
||||||
header-text-direction="center" body-text-direction="center" rowsPerPageMessage="تعداد سطر"
|
|
||||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از" :loading="loading">
|
|
||||||
<template #item-operation="{ code }">
|
|
||||||
<div class="dropdown-center">
|
|
||||||
<button aria-expanded="false" aria-haspopup="true" class="btn btn-sm btn-link"
|
|
||||||
data-bs-toggle="dropdown" id="dropdown-align-center-alt-primary" type="button">
|
|
||||||
<i class="fa-solid fa-ellipsis"></i>
|
|
||||||
</button>
|
|
||||||
<div aria-labelledby="dropdown-align-center-outline-primary" class="dropdown-menu dropdown-menu-end"
|
|
||||||
style="">
|
|
||||||
<router-link class="dropdown-item" :to="'/acc/storeroom/ticket/view/' + code">
|
|
||||||
<i class="fa fa-eye text-success pe-2"></i>
|
|
||||||
مشاهده
|
|
||||||
</router-link>
|
|
||||||
<button type="button" @click="deleteTicket('input', code)" class="dropdown-item text-danger">
|
|
||||||
<i class="fa fa-trash pe-2"></i>
|
|
||||||
حذف
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</EasyDataTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.v-data-table {
|
||||||
|
direction: rtl;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* استایل برای وسطچین کردن همه سلولهای جدول */
|
||||||
|
:deep(.v-data-table-header th) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table__wrapper table td) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,26 +1,73 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineComponent, ref} from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from "axios";
|
import axios from 'axios'
|
||||||
import Swal from "sweetalert2";
|
import { useRouter } from 'vue-router'
|
||||||
export default defineComponent({
|
|
||||||
name: "viewInvoice",
|
|
||||||
components:{
|
|
||||||
|
|
||||||
},
|
interface Business {
|
||||||
watch:{
|
legal_name: string
|
||||||
|
}
|
||||||
|
|
||||||
},
|
interface Storeroom {
|
||||||
data:()=>{return{
|
manager: string
|
||||||
loading:ref(false),
|
}
|
||||||
bid:{
|
|
||||||
legal_name:'',
|
interface Ticket {
|
||||||
},
|
id: number
|
||||||
item:{
|
date: string | null
|
||||||
|
code: string | null
|
||||||
|
des: string
|
||||||
|
type: string
|
||||||
|
typeString: string
|
||||||
|
storeroom: Storeroom
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
nikename: string | null
|
||||||
|
mobile: string
|
||||||
|
address: string
|
||||||
|
tel: string
|
||||||
|
codeeqtesadi: string
|
||||||
|
keshvar: string
|
||||||
|
ostan: string
|
||||||
|
shahr: string
|
||||||
|
postalcode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Commodity {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
unit: {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Row {
|
||||||
|
commodity: Commodity
|
||||||
|
count: number
|
||||||
|
hesabdariCount: number
|
||||||
|
remain: number
|
||||||
|
des: string
|
||||||
|
referal: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
ticket: Ticket
|
||||||
|
rows: Row[]
|
||||||
|
person: Person
|
||||||
|
transferType: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
const bid = ref<Business>({ legal_name: '' })
|
||||||
|
const item = ref<Item>({
|
||||||
ticket: {
|
ticket: {
|
||||||
id: 0,
|
id: 0,
|
||||||
date: null,
|
date: null,
|
||||||
code: null,
|
code: null,
|
||||||
des: '',
|
des: '',
|
||||||
|
type: '',
|
||||||
|
typeString: '',
|
||||||
storeroom: {
|
storeroom: {
|
||||||
manager: ''
|
manager: ''
|
||||||
}
|
}
|
||||||
|
@ -34,176 +81,223 @@ export default defineComponent({
|
||||||
codeeqtesadi: '',
|
codeeqtesadi: '',
|
||||||
keshvar: '',
|
keshvar: '',
|
||||||
ostan: '',
|
ostan: '',
|
||||||
shahr: ''
|
shahr: '',
|
||||||
|
postalcode: ''
|
||||||
},
|
},
|
||||||
},
|
transferType: null
|
||||||
headers: [
|
})
|
||||||
{ text: "کالا", value: "commodity" },
|
|
||||||
{ text: "تعداد", value: "count" },
|
const headers = [
|
||||||
{ text: "مورد نیاز", value: "hesabdariCount" },
|
{ title: "", key: "data-table-expand" },
|
||||||
{ text: "باقیمانده", value: "remain" },
|
{ title: "کالا", key: "commodity" },
|
||||||
|
{ title: "تعداد", key: "count" },
|
||||||
|
{ title: "مورد نیاز", key: "hesabdariCount" },
|
||||||
|
{ title: "باقیمانده", key: "remain" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [ticketResponse, businessResponse] = await Promise.all([
|
||||||
|
axios.post(`/api/storeroom/tickets/info/${router.currentRoute.value.params.id}`),
|
||||||
|
axios.post(`/api/business/get/info/${localStorage.getItem('activeBid')}`)
|
||||||
|
])
|
||||||
|
|
||||||
|
item.value.ticket = ticketResponse.data.ticket
|
||||||
|
item.value.person = ticketResponse.data.person
|
||||||
|
item.value.transferType = ticketResponse.data.transferType
|
||||||
|
item.value.rows = ticketResponse.data.commodities
|
||||||
|
|
||||||
|
bid.value = businessResponse.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadData() {
|
|
||||||
this.loading = true;
|
|
||||||
axios.post('/api/storeroom/tickets/info/' + this.$route.params.id).then((response) => {
|
|
||||||
this.item.ticket = response.data.ticket;
|
|
||||||
this.item.person = response.data.person;
|
|
||||||
this.item.transferType = response.data.transferType;
|
|
||||||
this.item.rows = response.data.commodities;
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
axios.post('/api/business/get/info/' + localStorage.getItem('activeBid')).then((response) => {
|
|
||||||
this.bid = response.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
printInvoice() {
|
|
||||||
axios.post('/api/storeroom/print/ticket', {
|
|
||||||
'code': this.$route.params.id,
|
|
||||||
'type': this.item.ticket.type
|
|
||||||
}).then((response) => {
|
|
||||||
this.printID = response.data.id;
|
|
||||||
window.open(this.$API_URL + '/front/print/' + this.printID, '_blank', 'noreferrer');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
mounted() {
|
const printInvoice = async () => {
|
||||||
this.loadData();
|
try {
|
||||||
|
const response = await axios.post('/api/storeroom/print/ticket', {
|
||||||
|
code: router.currentRoute.value.params.id,
|
||||||
|
type: item.value.ticket.type
|
||||||
|
})
|
||||||
|
window.open(`${import.meta.env.VITE_API_URL}/front/print/${response.data.id}`, '_blank', 'noreferrer')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error printing invoice:', error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full">
|
<v-toolbar color="toolbar" title="مشاهده و چاپ حواله انبار">
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light">
|
<template v-slot:prepend>
|
||||||
<h3 class="block-title text-primary-dark">
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
<button @click="$router.back()" type="button"
|
<template v-slot:activator="{ props }">
|
||||||
class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
<i class="fas fa-file-invoice-dollar"></i>
|
|
||||||
مشاهده و چاپ حواله انبار
|
|
||||||
</h3>
|
|
||||||
<div class="block-options">
|
|
||||||
<button class="btn btn-sm btn-primary mx-2" @click="printInvoice()" type="button">
|
|
||||||
<i class="si si-printer me-1"></i>
|
|
||||||
<span class="d-none d-sm-inline-block">چاپ</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<b class="ps-2">اطلاعات حواله انبار</b>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6 col-md-2">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">شماره</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.ticket.code" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-2">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">نوع</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.ticket.typeString" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-2">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">تاریخ</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.ticket.date" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-6">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">شرح</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.ticket.des" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<b class="ps-2">طرف حساب</b>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6 col-md-4">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">نام</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.person.nikename" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-4">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">موبایل</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.person.mobile" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-4">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">تلفن</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.person.tel" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-3">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">کد پستی</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.person.postalcode" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-9">
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">آدرس</span>
|
|
||||||
<input type="text" readonly="readonly" v-model="item.person.address" class="form-control"
|
|
||||||
aria-describedby="inputGroup-sizing-sm">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<b class="ps-2">اقلام</b>
|
|
||||||
<EasyDataTable table-class-name="customize-table" :headers="headers" :items="item.rows" show-index alternating
|
|
||||||
theme-color="#1d90ff" header-text-direction="center" body-text-direction="center" rowsPerPageMessage="تعداد سطر"
|
|
||||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از" :loading="loading">
|
|
||||||
<template #item-count="{ count, commodity }">
|
|
||||||
{{ count }} {{ commodity.unit.name }}
|
|
||||||
</template>
|
</template>
|
||||||
<template #item-commodity="{ commodity }">
|
</v-tooltip>
|
||||||
{{ commodity.code }} {{ commodity.name }}
|
|
||||||
</template>
|
</template>
|
||||||
<template #expand="{ des, referal }">
|
<v-spacer />
|
||||||
<div class="p-1 m-0 text-start">
|
<v-tooltip text="چاپ" location="bottom">
|
||||||
شرح
|
<template v-slot:activator="{ props }">
|
||||||
:
|
<v-btn
|
||||||
{{ des }}
|
v-bind="props"
|
||||||
<br />
|
@click="printInvoice"
|
||||||
ارجاع:
|
color="primary"
|
||||||
{{ referal }}
|
icon="mdi-printer"
|
||||||
</div>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</EasyDataTable>
|
</v-tooltip>
|
||||||
</div>
|
</v-toolbar>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<v-container>
|
||||||
|
<v-card variant="outlined" class="mb-4">
|
||||||
|
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||||
|
<v-icon start>mdi-information</v-icon>
|
||||||
|
اطلاعات حواله انبار
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.ticket.code"
|
||||||
|
label="شماره"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.ticket.typeString"
|
||||||
|
label="نوع"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.ticket.date"
|
||||||
|
label="تاریخ"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.ticket.des"
|
||||||
|
label="شرح"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-card variant="outlined" class="mb-4">
|
||||||
|
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||||
|
<v-icon start>mdi-account</v-icon>
|
||||||
|
طرف حساب
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.person.nikename"
|
||||||
|
label="نام"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.person.mobile"
|
||||||
|
label="موبایل"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.person.tel"
|
||||||
|
label="تلفن"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="3">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.person.postalcode"
|
||||||
|
label="کد پستی"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="9">
|
||||||
|
<v-text-field
|
||||||
|
v-model="item.person.address"
|
||||||
|
label="آدرس"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||||
|
<v-icon start>mdi-package-variant</v-icon>
|
||||||
|
اقلام
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="item.rows"
|
||||||
|
:loading="loading"
|
||||||
|
class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }"
|
||||||
|
density="compact"
|
||||||
|
show-expand
|
||||||
|
>
|
||||||
|
<template v-slot:item.count="{ item }">
|
||||||
|
{{ item.count }} {{ item.commodity.unit.name }}
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.commodity="{ item }">
|
||||||
|
{{ item.commodity.code }} {{ item.commodity.name }}
|
||||||
|
</template>
|
||||||
|
<template v-slot:expanded-row="{ columns, item }">
|
||||||
|
<tr>
|
||||||
|
<td :colspan="columns.length">
|
||||||
|
<div class="pa-2 text-right">
|
||||||
|
<div>شرح: {{ item.des }}</div>
|
||||||
|
<div>ارجاع: {{ item.referal }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
table{
|
@media print {
|
||||||
font-size: small;
|
|
||||||
border: 1px solid gray;
|
|
||||||
}
|
|
||||||
.table-header{
|
|
||||||
background-color: lightgray;
|
|
||||||
}
|
|
||||||
.c-print{
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print
|
|
||||||
{
|
|
||||||
@page {
|
@page {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -1,96 +1,314 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full ">
|
<v-toolbar color="toolbar" title="انبارها">
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
<template v-slot:prepend>
|
||||||
<h3 class="block-title text-primary-dark">
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
<button @click="$router.back()" type="button" class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
<template v-slot:activator="{ props }">
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
|
||||||
</button>
|
</template>
|
||||||
<i class="mx-2 fa fa-boxes-stacked"></i>
|
</v-tooltip>
|
||||||
انبارها </h3>
|
</template>
|
||||||
<div class="block-options">
|
<v-spacer />
|
||||||
<router-link to="/acc/storeroom/mod/" class="btn btn-sm btn-primary ms-1">
|
|
||||||
<span class="fa fa-plus fw-bolder"></span>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="block-content pt-1 pb-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12 m-0 p-0">
|
|
||||||
<div class="mb-1">
|
|
||||||
<div class="input-group input-group-sm">
|
|
||||||
<span class="input-group-text"><i class="fa fa-search"></i></span>
|
|
||||||
<input v-model="searchValue" class="form-control" type="text" placeholder="جست و جو ...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EasyDataTable table-class-name="customize-table"
|
|
||||||
multi-sort
|
|
||||||
show-index
|
|
||||||
alternating
|
|
||||||
|
|
||||||
:search-value="searchValue"
|
<v-slide-group show-arrows>
|
||||||
:headers="headers"
|
<v-slide-group-item>
|
||||||
:items="items"
|
<v-tooltip text="افزودن جدید" location="bottom">
|
||||||
theme-color="#1d90ff"
|
<template v-slot:activator="{ props }">
|
||||||
header-text-direction="center"
|
<v-btn v-bind="props" icon="mdi-plus" color="primary" to="/acc/storeroom/mod/" />
|
||||||
body-text-direction="center"
|
</template>
|
||||||
rowsPerPageMessage="تعداد سطر"
|
</v-tooltip>
|
||||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
|
</v-slide-group-item>
|
||||||
rowsOfPageSeparatorMessage="از"
|
|
||||||
|
<v-slide-group-item>
|
||||||
|
<v-tooltip text="تنظیمات ستونها" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-table-cog" color="primary" @click="showColumnDialog = true" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-slide-group-item>
|
||||||
|
</v-slide-group>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="search"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
color="green"
|
||||||
|
class="mb-0 pt-0 rounded-0"
|
||||||
|
hide-details="auto"
|
||||||
|
density="compact"
|
||||||
|
:rounded="false"
|
||||||
|
placeholder="جست و جو ..."
|
||||||
|
clearable
|
||||||
>
|
>
|
||||||
<template #item-operation="{ id }">
|
<template v-slot:prepend-inner>
|
||||||
<router-link :to="'/acc/storeroom/mod/' + id">
|
<v-tooltip location="bottom" text="جستجو">
|
||||||
<i class="fa fa-edit px-2"></i>
|
<template v-slot:activator="{ props }">
|
||||||
</router-link>
|
<v-icon v-bind="props" color="danger" icon="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template #item-active="{ active }">
|
</v-tooltip>
|
||||||
<label class="text-primary" v-if="active">فعال</label>
|
|
||||||
<label class="text-danger" v-else>غیرفعال</label>
|
|
||||||
</template>
|
</template>
|
||||||
</EasyDataTable>
|
</v-text-field>
|
||||||
</div>
|
|
||||||
</div>
|
<v-data-table
|
||||||
</div>
|
:headers="visibleHeaders"
|
||||||
</div>
|
:items="items"
|
||||||
|
:loading="loading"
|
||||||
|
:search="search"
|
||||||
|
class="elevation-1 text-center"
|
||||||
|
:header-props="{ class: 'custom-header' }"
|
||||||
|
>
|
||||||
|
<template v-slot:item="{ item }">
|
||||||
|
<tr>
|
||||||
|
<td v-if="isColumnVisible('operation')" class="text-center">
|
||||||
|
<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/storeroom/mod/' + item.id">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon icon="mdi-pencil" />
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>ویرایش</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item @click="confirmDelete(item.id)">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="error" icon="mdi-delete" />
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>حذف</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</td>
|
||||||
|
<td v-if="isColumnVisible('id')" class="text-center">{{ formatNumber(item.id) }}</td>
|
||||||
|
<td v-if="isColumnVisible('name')" class="text-center">{{ item.name }}</td>
|
||||||
|
<td v-if="isColumnVisible('manager')" class="text-center">{{ item.manager }}</td>
|
||||||
|
<td v-if="isColumnVisible('tel')" class="text-center">{{ item.tel }}</td>
|
||||||
|
<td v-if="isColumnVisible('adr')" class="text-center">{{ item.adr }}</td>
|
||||||
|
<td v-if="isColumnVisible('active')" class="text-center">
|
||||||
|
<v-chip
|
||||||
|
:color="item.active ? 'success' : 'error'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ item.active ? 'فعال' : 'غیرفعال' }}
|
||||||
|
</v-chip>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
|
||||||
|
<v-dialog v-model="showColumnDialog" max-width="500">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="toolbar" title="مدیریت ستونها">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon @click="showColumnDialog = false">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col v-for="header in allHeaders" :key="header.key" cols="12" sm="6">
|
||||||
|
<v-checkbox
|
||||||
|
v-model="header.visible"
|
||||||
|
:label="header.title"
|
||||||
|
@change="updateColumnVisibility"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-dialog v-model="deleteDialog.show" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h6">
|
||||||
|
تأیید حذف
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
آیا برای حذف انبار مطمئن هستید؟
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" variant="text" @click="deleteDialog.show = false">خیر</v-btn>
|
||||||
|
<v-btn color="error" variant="text" @click="deleteItem">بله</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-dialog v-model="messageDialog.show" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title :class="messageDialog.color + ' text-h6'">
|
||||||
|
{{ messageDialog.title }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text class="pt-4">
|
||||||
|
{{ messageDialog.message }}
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" variant="text" @click="messageDialog.show = false">قبول</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import axios from "axios";
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import Swal from "sweetalert2";
|
import axios from 'axios';
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
export default {
|
// Refs
|
||||||
name: "list",
|
const loading = ref(false);
|
||||||
data: ()=>{return {
|
const items = ref([]);
|
||||||
printID:'',
|
const search = ref('');
|
||||||
searchValue: '',
|
const showColumnDialog = ref(false);
|
||||||
loading : ref(true),
|
|
||||||
items:[],
|
// دیالوگها
|
||||||
headers: [
|
const deleteDialog = ref({
|
||||||
{ text: "کد", value: "id" },
|
show: false,
|
||||||
{ text: "نام انبار", value: "name", sortable: true},
|
id: null
|
||||||
{ text: "انباردار", value: "manager", sortable: true},
|
});
|
||||||
{ text: "تلفن", value: "tel", sortable: true},
|
|
||||||
{ text: "آدرس", value: "adr"},
|
const messageDialog = ref({
|
||||||
{ text: "وضعیت", value: "active"},
|
show: false,
|
||||||
{ text: "عملیات", value: "operation"},
|
title: '',
|
||||||
]
|
message: '',
|
||||||
}},
|
color: 'primary'
|
||||||
methods: {
|
});
|
||||||
loadData(){
|
|
||||||
axios.post('/api/storeroom/list/all')
|
// تابع فرمتکننده اعداد
|
||||||
.then((response)=>{
|
const formatNumber = (value) => {
|
||||||
this.items = response.data.data;
|
if (!value) return '0';
|
||||||
this.loading = false;
|
return Number(value).toLocaleString('fa-IR');
|
||||||
})
|
};
|
||||||
},
|
|
||||||
},
|
// تعریف همه ستونها
|
||||||
beforeMount() {
|
const allHeaders = ref([
|
||||||
this.loadData();
|
{ title: "عملیات", key: "operation", align: 'center', sortable: false, width: 100, visible: true },
|
||||||
|
{ title: "کد", key: "id", align: 'center', sortable: true, width: 100, visible: true },
|
||||||
|
{ title: "نام انبار", key: "name", align: 'center', sortable: true, width: 140, visible: true },
|
||||||
|
{ title: "انباردار", key: "manager", align: 'center', sortable: true, width: 120, visible: true },
|
||||||
|
{ title: "تلفن", key: "tel", align: 'center', sortable: true, width: 120, visible: true },
|
||||||
|
{ title: "آدرس", key: "adr", align: 'center', sortable: true, width: 160, visible: true },
|
||||||
|
{ title: "وضعیت", key: "active", align: 'center', sortable: true, width: 100, visible: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ستونهای قابل نمایش
|
||||||
|
const visibleHeaders = computed(() => {
|
||||||
|
return allHeaders.value.filter(header => header.visible);
|
||||||
|
});
|
||||||
|
|
||||||
|
// بررسی نمایش ستون
|
||||||
|
const isColumnVisible = (key) => {
|
||||||
|
return allHeaders.value.find(header => header.key === key)?.visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
// کلید ذخیرهسازی در localStorage
|
||||||
|
const LOCAL_STORAGE_KEY = 'hesabix_storeroom_table_columns';
|
||||||
|
|
||||||
|
// لود تنظیمات ستونها
|
||||||
|
const loadColumnSettings = () => {
|
||||||
|
const savedSettings = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||||
|
if (savedSettings) {
|
||||||
|
const visibleColumns = JSON.parse(savedSettings);
|
||||||
|
allHeaders.value.forEach(header => {
|
||||||
|
header.visible = visibleColumns.includes(header.key);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ذخیره تنظیمات ستونها
|
||||||
|
const updateColumnVisibility = () => {
|
||||||
|
const visibleColumns = allHeaders.value
|
||||||
|
.filter(header => header.visible)
|
||||||
|
.map(header => header.key);
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(visibleColumns));
|
||||||
|
};
|
||||||
|
|
||||||
|
// نمایش پیام
|
||||||
|
const showMessage = (message, title = 'پیام', color = 'primary') => {
|
||||||
|
messageDialog.value = {
|
||||||
|
show: true,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
color
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// تأیید حذف
|
||||||
|
const confirmDelete = (id) => {
|
||||||
|
deleteDialog.value = {
|
||||||
|
show: true,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// بارگذاری دادهها
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/storeroom/list/all');
|
||||||
|
items.value = response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
showMessage('خطا در بارگذاری دادهها: ' + error.message, 'خطا', 'error');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// حذف آیتم
|
||||||
|
const deleteItem = async () => {
|
||||||
|
const id = deleteDialog.value.id;
|
||||||
|
deleteDialog.value.show = false;
|
||||||
|
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const response = await axios.post(`/api/storeroom/delete/${id}`);
|
||||||
|
|
||||||
|
if (response.data.result === 1) {
|
||||||
|
items.value = items.value.filter(item => item.id !== id);
|
||||||
|
showMessage('انبار با موفقیت حذف شد.', 'موفقیت', 'success');
|
||||||
|
} else if (response.data.result === 2) {
|
||||||
|
showMessage('انبار به دلیل داشتن موجودی قابل حذف نیست.', 'خطا', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting item:', error);
|
||||||
|
showMessage('خطا در حذف آیتم: ' + error.message, 'خطا', 'error');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// مانت کامپوننت
|
||||||
|
onMounted(() => {
|
||||||
|
loadColumnSettings();
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
|
.v-data-table {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* استایل برای وسطچین کردن همه سلولهای جدول */
|
||||||
|
:deep(.v-data-table-header th) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-data-table__wrapper table td) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* استایل برای رنگهای متن */
|
||||||
|
.text-success {
|
||||||
|
color: #4caf50 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-error {
|
||||||
|
color: #ff5252 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,142 +1,195 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full ">
|
<v-toolbar color="toolbar" title="مشخصات انبار">
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
<template v-slot:prepend>
|
||||||
<h3 class="block-title text-primary-dark">
|
<v-tooltip text="بازگشت" location="bottom">
|
||||||
<button type="button" @click="$router.back()" class="btn text-warning mx-2 px-2">
|
<template v-slot:activator="{ props }">
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
|
||||||
</button>
|
icon="mdi-arrow-right" />
|
||||||
مشخصات انبار </h3>
|
</template>
|
||||||
<div class="block-options">
|
</v-tooltip>
|
||||||
<button @click="save()" type="button" class="btn btn-sm btn-alt-primary">
|
</template>
|
||||||
<i class="fa fa-save me-2"></i>
|
<v-spacer />
|
||||||
ثبت
|
<v-btn-toggle
|
||||||
</button>
|
v-model="data.active"
|
||||||
</div>
|
mandatory
|
||||||
</div>
|
density="compact"
|
||||||
<div class="block-content py-3 vl-parent">
|
class="mx-2"
|
||||||
<loading color="blue" loader="dots" v-model:active="isLoading" :is-full-page="false"/>
|
>
|
||||||
<div class="container">
|
<v-btn
|
||||||
<div class="row py-3">
|
:value="true"
|
||||||
<div class="col-sm-12 col-md-12">
|
size="small"
|
||||||
<div>
|
:class="data.active ? 'bg-success' : ''"
|
||||||
<label class="me-4 text-primary">وضعیت انبار</label>
|
>
|
||||||
<div class="form-check form-check-inline">
|
<v-icon size="small" start>mdi-check-circle</v-icon>
|
||||||
<input v-model="this.data.active" class="form-check-input" type="radio" value="true">
|
فعال
|
||||||
<label class="form-check-label" for="inlineCheckbox1">فعال</label>
|
</v-btn>
|
||||||
</div>
|
<v-btn
|
||||||
<div class="form-check form-check-inline">
|
:value="false"
|
||||||
<input v-model="this.data.active" class="form-check-input" type="radio" value="false">
|
size="small"
|
||||||
<label class="form-check-label" for="inlineCheckbox2">غیرفعال</label>
|
:class="!data.active ? 'bg-error' : ''"
|
||||||
</div>
|
>
|
||||||
</div>
|
<v-icon size="small" start>mdi-close-circle</v-icon>
|
||||||
</div>
|
غیرفعال
|
||||||
</div>
|
</v-btn>
|
||||||
<div class="row">
|
</v-btn-toggle>
|
||||||
<div class="col-sm-12 col-md-6">
|
<v-tooltip text="ثبت" location="bottom">
|
||||||
<div class="form-floating mb-4">
|
<template v-slot:activator="{ props }">
|
||||||
<input v-model="data.name" class="form-control" type="text">
|
<v-btn v-bind="props" color="primary" icon="mdi-content-save" @click="save" :loading="isLoading" />
|
||||||
<label class="form-label"><span class="text-danger">(لازم)</span> نام انبار</label>
|
</template>
|
||||||
</div>
|
</v-tooltip>
|
||||||
<div class="form-floating mb-4">
|
</v-toolbar>
|
||||||
<input v-model="data.tel" class="form-control" type="text">
|
<v-container fluid>
|
||||||
<label class="form-label">تلفن</label>
|
<v-row>
|
||||||
</div>
|
<v-col cols="12" md="6">
|
||||||
</div>
|
<v-text-field
|
||||||
<div class="col-sm-12 col-md-6">
|
v-model="data.name"
|
||||||
<div class="form-floating mb-4">
|
label="نام انبار"
|
||||||
<input v-model="data.manager" class="form-control" type="text">
|
:rules="[v => !!v || 'نام انبار الزامی است']"
|
||||||
<label class="form-label">انباردار</label>
|
required
|
||||||
</div>
|
variant="outlined"
|
||||||
</div>
|
density="compact"
|
||||||
<div class="col-sm-12 col-md-12">
|
>
|
||||||
<div class="form-floating mb-4">
|
<template v-slot:label>
|
||||||
<input v-model="data.adr" class="form-control" type="text">
|
<span class="text-danger">(لازم)</span> نام انبار
|
||||||
<label class="form-label">آدرس</label>
|
</template>
|
||||||
</div>
|
</v-text-field>
|
||||||
</div>
|
</v-col>
|
||||||
</div>
|
|
||||||
</div>
|
<v-col cols="12" md="6">
|
||||||
</div>
|
<v-text-field
|
||||||
</div>
|
v-model="data.manager"
|
||||||
|
label="انباردار"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="data.tel"
|
||||||
|
label="تلفن"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="data.adr"
|
||||||
|
label="آدرس"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import axios from "axios";
|
import { ref, onMounted } from 'vue';
|
||||||
import Swal from "sweetalert2";
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import Loading from 'vue-loading-overlay';
|
import axios from 'axios';
|
||||||
import 'vue-loading-overlay/dist/css/index.css';
|
|
||||||
import {Money3} from "v-money3";
|
|
||||||
|
|
||||||
export default {
|
const route = useRoute();
|
||||||
name: "mod",
|
const router = useRouter();
|
||||||
components: {
|
|
||||||
Loading,
|
// Refs
|
||||||
Money3
|
const isLoading = ref(false);
|
||||||
},
|
const data = ref({
|
||||||
data: ()=>{return{
|
|
||||||
isLoading: false,
|
|
||||||
units:'',
|
|
||||||
data: {
|
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
manager: '',
|
manager: '',
|
||||||
active: true,
|
active: true,
|
||||||
tel: '',
|
tel: '',
|
||||||
adr: '',
|
adr: '',
|
||||||
},
|
|
||||||
}},
|
|
||||||
mounted() {
|
|
||||||
this.loadData(this.$route.params.id);
|
|
||||||
},
|
|
||||||
beforeRouteUpdate(to,from){
|
|
||||||
this.loadData(to.params.id);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadData(id = '') {
|
|
||||||
this.isLoading = true;
|
|
||||||
if (id != '') {
|
|
||||||
//load user info
|
|
||||||
this.isLoading = true;
|
|
||||||
axios.post('/api/storeroom/info/' + id).then((response) => {
|
|
||||||
this.data = response.data;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
color: 'primary'
|
||||||
|
});
|
||||||
|
|
||||||
|
// نمایش پیام
|
||||||
|
const showMessage = (message, color = 'primary') => {
|
||||||
|
snackbar.value = {
|
||||||
|
show: true,
|
||||||
|
message,
|
||||||
|
color
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// بارگذاری دادهها
|
||||||
|
const loadData = async (id = '') => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/storeroom/info/' + id);
|
||||||
|
data.value = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
showMessage('خطا در بارگذاری دادهها: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
};
|
||||||
},
|
|
||||||
save() {
|
// ذخیره دادهها
|
||||||
if (this.data.name.length === 0)
|
const save = async () => {
|
||||||
Swal.fire({
|
if (!data.value.name) {
|
||||||
text: 'نام کالا یا خدمات الزامی است.',
|
showMessage('نام انبار الزامی است.', 'error');
|
||||||
icon: 'error',
|
return;
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
|
||||||
else {
|
|
||||||
this.isLoading = true;
|
|
||||||
axios.post('/api/storeroom/mod/' + this.data.id, this.data).then((response) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
if (response.data.result == 2) {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'قبلا ثبت شده است.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Swal.fire({
|
|
||||||
text: 'مشخصات انبار ثبت شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then(() => {
|
|
||||||
this.$router.push('/acc/storeroom/list')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/storeroom/mod/' + data.value.id, data.value);
|
||||||
|
|
||||||
|
if (response.data.result === 2) {
|
||||||
|
showMessage('قبلا ثبت شده است.', 'error');
|
||||||
|
} else {
|
||||||
|
showMessage('مشخصات انبار ثبت شد.', 'success');
|
||||||
|
// تاخیر در انتقال به صفحه لیست برای نمایش اسنکبار
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acc/storeroom/list');
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving data:', error);
|
||||||
|
showMessage('خطا در ذخیره دادهها: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// مانت کامپوننت
|
||||||
|
onMounted(() => {
|
||||||
|
loadData(route.params.id);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
|
.v-radio-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in a new issue