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));
|
||||
}
|
||||
|
||||
#[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')]
|
||||
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]);
|
||||
}
|
||||
|
||||
#[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
|
||||
*/
|
||||
|
|
|
@ -135,4 +135,137 @@ class CashdeskController extends AbstractController
|
|||
$log->insert('بانکداری', ' صندوق با نام ' . $name . ' حذف شد. ', $this->getUser(), $acc['bid']->getId());
|
||||
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'])]
|
||||
public function getHesabdariTableChildren(int $id, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
#[Route('/api/hesabdari/tables/tree', name: 'get_hesabdari_table_tree', methods: ['GET'])]
|
||||
public function getHesabdariTableTree(Access $access, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('accounting');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$node = $entityManager->getRepository(HesabdariTable::class)->find($id);
|
||||
if (!$node) {
|
||||
return $this->json(['Success' => false, 'message' => 'نود مورد نظر یافت نشد'], 404);
|
||||
$depth = (int) $request->query->get('depth', 2); // عمق پیشفرض 2
|
||||
$rootId = (int) $request->query->get('rootId', 1); // گره ریشه پیشفرض
|
||||
|
||||
$root = $entityManager->getRepository(HesabdariTable::class)->find($rootId);
|
||||
if (!$root) {
|
||||
return $this->json(['Success' => false, 'message' => 'نود ریشه یافت نشد'], 404);
|
||||
}
|
||||
|
||||
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||
'upper' => $node,
|
||||
'bid' => [$acc['bid']->getId(), null] // حسابهای عمومی و خصوصی
|
||||
]);
|
||||
$buildTree = function ($node, $depth, $currentDepth = 0) use ($entityManager, $acc, &$buildTree) {
|
||||
if ($currentDepth >= $depth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($children as $child) {
|
||||
$result[] = [
|
||||
'id' => $child->getId(),
|
||||
'name' => $child->getName(),
|
||||
'code' => $child->getCode(),
|
||||
'type' => $child->getType(),
|
||||
'children' => $this->hasChild($entityManager, $child) ? [] : null
|
||||
];
|
||||
$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, $depth, $currentDepth + 1),
|
||||
];
|
||||
$result[] = $childData;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return $this->json(['Success' => true, 'data' => $result]);
|
||||
$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());
|
||||
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' %}
|
||||
|
||||
{% 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;">
|
||||
<table style="width:100%; border-radius: 8px;">
|
||||
<table style="width:100%;">
|
||||
<tbody>
|
||||
<tr style="font-size:12px;">
|
||||
<td class="right" style="border: 1px solid black;">
|
||||
<td class="right">
|
||||
<p>
|
||||
<b>شماره سند:</b>
|
||||
{{ doc.code }}
|
||||
</p>
|
||||
</td>
|
||||
<td class="right" style="border: 1px solid black;">
|
||||
<td class="right">
|
||||
<p>
|
||||
<b>نوع سند:</b>
|
||||
{% if doc.type == 'cost' %}
|
||||
|
@ -34,7 +34,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr style="font-size:12px;">
|
||||
<td class="right" colspan="2" style="border: 1px solid black;">
|
||||
<td class="right" colspan="2">
|
||||
<p>
|
||||
<b>توضیحات:</b>
|
||||
{{ doc.des }}
|
||||
|
@ -88,9 +88,9 @@
|
|||
{% elseif item.bank %}
|
||||
{{item.bank.name}}
|
||||
{% elseif item.cashdesk %}
|
||||
{{item.salary.name}}
|
||||
{% elseif item.salary %}
|
||||
{{item.cashdesk.name}}
|
||||
{% elseif item.salary %}
|
||||
{{item.salary.name}}
|
||||
{% else %}
|
||||
{{item.ref.name}}
|
||||
{% endif %}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^22.14.1",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||
|
|
|
@ -1,303 +1,324 @@
|
|||
<template>
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
:close-on-content-click="false"
|
||||
location="bottom"
|
||||
width="400"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-text-field
|
||||
v-bind="props"
|
||||
:model-value="selectedAccountName"
|
||||
:label="label"
|
||||
:rules="rules"
|
||||
readonly
|
||||
hide-details
|
||||
density="compact"
|
||||
@click="openMenu"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</template>
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
:close-on-content-click="false"
|
||||
location="bottom"
|
||||
width="400"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-text-field
|
||||
v-bind="props"
|
||||
:model-value="selectedAccountName"
|
||||
:label="label"
|
||||
:rules="rules"
|
||||
readonly
|
||||
hide-details
|
||||
density="compact"
|
||||
@click="openMenu"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-progress-linear v-if="isLoading" indeterminate color="primary" />
|
||||
<v-card-text v-if="accountData.length > 0" class="pa-0">
|
||||
<div class="tree-container">
|
||||
<div
|
||||
v-for="item in accountData"
|
||||
:key="item.id"
|
||||
class="tree-node"
|
||||
:class="{ 'has-children': item.children && item.children.length > 0 }"
|
||||
>
|
||||
<div class="tree-node-content" @click="handleNodeClick(item)">
|
||||
<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>
|
||||
</v-card-text>
|
||||
<v-card-text v-else>
|
||||
در حال بارگذاری...
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-progress-linear v-if="isLoading" indeterminate color="primary" />
|
||||
<v-card-text v-if="accountData.length > 0" class="pa-0">
|
||||
<div class="tree-container">
|
||||
<tree-node
|
||||
v-for="node in accountData"
|
||||
:key="node.id"
|
||||
:node="node"
|
||||
:selected-id="selectedAccount?.id"
|
||||
@select="handleNodeSelect"
|
||||
@toggle="toggleNode"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text v-else>
|
||||
{{ isLoading ? 'در حال بارگذاری...' : 'هیچ حسابی یافت نشد' }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { debounce } from 'lodash'; // برای دیبانس کردن loadChildren
|
||||
<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>
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: 'حساب'
|
||||
},
|
||||
rules: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
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({
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: 'حساب',
|
||||
},
|
||||
rules: {
|
||||
type: Array as () => ValidationRule[],
|
||||
default: () => [],
|
||||
},
|
||||
initialAccount: {
|
||||
type: Object as () => AccountNode | null,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'select', 'tableType', 'accountSelected']);
|
||||
|
||||
const menu = ref(false);
|
||||
const accountData = ref<AccountNode[]>([]);
|
||||
const selectedAccount = ref<AccountNode | null>(null);
|
||||
const cache = ref(new Map<string, AccountNode[]>());
|
||||
const isLoading = ref(false);
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
text: '',
|
||||
color: 'success',
|
||||
});
|
||||
|
||||
const selectedAccountName = computed(() => {
|
||||
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 => {
|
||||
try {
|
||||
return decodeURIComponent(
|
||||
str.replace(/\\u([\dA-F]{4})/gi, (match, grp) =>
|
||||
String.fromCharCode(parseInt(grp, 16))
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('خطا در رمزگشایی یونیکد:', e);
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
// پردازش دادهها
|
||||
const processTreeData = (items: any[]): any[] => {
|
||||
return items.map((item) => {
|
||||
if (cache.value.has(`processed-${item.id}`)) {
|
||||
return cache.value.get(`processed-${item.id}`);
|
||||
}
|
||||
const processedItem = {
|
||||
...item,
|
||||
name: decodeUnicode(item.name),
|
||||
children: item.children ? processTreeData(item.children) : [],
|
||||
isOpen: false,
|
||||
tableType: item.type,
|
||||
};
|
||||
cache.value.set(`processed-${item.id}`, processedItem);
|
||||
return processedItem;
|
||||
});
|
||||
};
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'select']);
|
||||
// بارگذاری تمام دادهها
|
||||
const fetchAllHesabdariTables = async () => {
|
||||
// اگر دادهها در کش سراسری وجود دارند
|
||||
if (globalCache.data) {
|
||||
accountData.value = globalCache.data;
|
||||
return;
|
||||
}
|
||||
|
||||
const menu = ref(false);
|
||||
const accountData = ref([]);
|
||||
const selectedAccount = ref(null);
|
||||
const cache = ref(new Map());
|
||||
const isLoading = ref(false);
|
||||
// اگر در حال بارگذاری است، منتظر بمان
|
||||
if (globalCache.isLoading && globalCache.promise) {
|
||||
await globalCache.promise;
|
||||
accountData.value = globalCache.data || [];
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedAccountName = computed(() => {
|
||||
return selectedAccount.value?.name || '';
|
||||
});
|
||||
// شروع بارگذاری جدید
|
||||
globalCache.isLoading = true;
|
||||
isLoading.value = true;
|
||||
|
||||
// تابع برای رمزگشایی کاراکترهای یونیکد
|
||||
const decodeUnicode = (str: string): string => {
|
||||
try {
|
||||
return decodeURIComponent(
|
||||
str.replace(/\\u([\dA-F]{4})/gi, (match, grp) =>
|
||||
String.fromCharCode(parseInt(grp, 16))
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('خطا در رمزگشایی یونیکد:', e);
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
// پردازش دادهها برای رمزگشایی نامها
|
||||
const processTreeData = (items: any[]): any[] => {
|
||||
return items.map(item => {
|
||||
if (cache.value.has(`processed-${item.id}`)) {
|
||||
return cache.value.get(`processed-${item.id}`);
|
||||
try {
|
||||
globalCache.promise = new Promise(async (resolve) => {
|
||||
try {
|
||||
const response = await axios.get('/api/hesabdari/tables/all');
|
||||
if (response.data.Success && response.data.data) {
|
||||
const allNodes = processTreeData(response.data.data.children || []);
|
||||
globalCache.data = allNodes;
|
||||
accountData.value = allNodes;
|
||||
} else {
|
||||
showMessage('هیچ حسابی یافت نشد', 'warning');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('خطا در بارگذاری حسابها:', error);
|
||||
showMessage('خطا در بارگذاری حسابها', 'error');
|
||||
} finally {
|
||||
globalCache.isLoading = false;
|
||||
isLoading.value = false;
|
||||
resolve();
|
||||
}
|
||||
const processedItem = {
|
||||
...item,
|
||||
name: decodeUnicode(item.name),
|
||||
children: item.children ? processTreeData(item.children) : [],
|
||||
isOpen: false
|
||||
};
|
||||
cache.value.set(`processed-${item.id}`, processedItem);
|
||||
return processedItem;
|
||||
});
|
||||
};
|
||||
|
||||
// بارگذاری تنبل زیرشاخهها با دیبانس
|
||||
const loadChildren = debounce(async (node: any) => {
|
||||
if (cache.value.has(node.id)) {
|
||||
node.children = cache.value.get(node.id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await axios.get(`/api/hesabdari/tables/${node.id}/children`);
|
||||
if (response.data.Success) {
|
||||
const children = processTreeData(response.data.data || []);
|
||||
node.children = children;
|
||||
cache.value.set(node.id, children);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`خطا در بارگذاری زیرشاخههای گره ${node.id}:`, error);
|
||||
}
|
||||
}, 300);
|
||||
await globalCache.promise;
|
||||
} catch (error) {
|
||||
console.error('خطا در بارگذاری حسابها:', error);
|
||||
showMessage('خطا در بارگذاری حسابها', 'error');
|
||||
globalCache.isLoading = false;
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleNode = (node: any) => {
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.isOpen = !node.isOpen;
|
||||
if (node.isOpen && (!node.children || node.children.length === 0)) {
|
||||
loadChildren(node);
|
||||
// جستجوی حساب در دادههای موجود
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// مدیریت انتخاب آیتمها
|
||||
const handleNodeClick = (node: any) => {
|
||||
selectedAccount.value = node;
|
||||
emit('update:modelValue', node.id);
|
||||
emit('select', node);
|
||||
menu.value = false;
|
||||
};
|
||||
|
||||
// باز کردن منو
|
||||
const openMenu = () => {
|
||||
menu.value = true;
|
||||
if (!accountData.value.length && !isLoading.value) {
|
||||
fetchHesabdariTables();
|
||||
// لود دادهها اگر هنوز لود نشدهاند
|
||||
if (!accountData.value.length) {
|
||||
await fetchAllHesabdariTables();
|
||||
}
|
||||
};
|
||||
|
||||
// بارگذاری اولیه گرههای ریشه
|
||||
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);
|
||||
// جستجوی حساب در دادهها
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('خطا در بارگذاری حسابها:', error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// دیباگ تعداد مونتها
|
||||
onMounted(() => {
|
||||
fetchHesabdariTables();
|
||||
});
|
||||
|
||||
// بررسی تغییرات در vue-router
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
console.log('modelValue تغییر کرد، احتمالاً به دلیل ناوبری');
|
||||
} else {
|
||||
if (selectedAccount.value) {
|
||||
selectedAccount.value = null;
|
||||
emit('select', null);
|
||||
emit('accountSelected', null);
|
||||
emit('tableType', null);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
};
|
||||
|
||||
.tree-node {
|
||||
margin-left: 24px;
|
||||
}
|
||||
// تغییر وضعیت گره
|
||||
const toggleNode = (node: any) => {
|
||||
node.isOpen = !node.isOpen;
|
||||
};
|
||||
|
||||
.tree-node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
// مدیریت انتخاب گره
|
||||
const handleNodeSelect = (node: any) => {
|
||||
selectedAccount.value = node;
|
||||
emit('update:modelValue', node.id);
|
||||
emit('select', node);
|
||||
emit('accountSelected', node);
|
||||
if (node.tableType) {
|
||||
emit('tableType', node.tableType);
|
||||
}
|
||||
};
|
||||
|
||||
.tree-node-content:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
// باز کردن منو
|
||||
const openMenu = () => {
|
||||
menu.value = true;
|
||||
if (!accountData.value.length && !isLoading.value) {
|
||||
fetchAllHesabdariTables();
|
||||
}
|
||||
};
|
||||
|
||||
.tree-node-toggle {
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
// اصلاح watch برای modelValue
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newVal, oldVal) => {
|
||||
if (newVal === oldVal || newVal === selectedAccount.value?.id) return;
|
||||
await initializeAccount();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
.tree-node-icon {
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
// اضافه کردن watch برای initialAccount
|
||||
watch(
|
||||
() => props.initialAccount,
|
||||
async (newVal) => {
|
||||
if (newVal && props.modelValue) {
|
||||
await initializeAccount();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
.tree-node-label {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Vazir', sans-serif;
|
||||
onMounted(() => {
|
||||
if (props.modelValue || props.initialAccount) {
|
||||
initializeAccount();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
.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 scoped>
|
||||
.tree-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
</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"
|
||||
:rules="combinedRules"
|
||||
:label="label"
|
||||
class=""
|
||||
class="my-0"
|
||||
prepend-inner-icon="mdi-package-variant"
|
||||
clearable
|
||||
@click:clear="clearSelection"
|
||||
:loading="loading"
|
||||
@keydown.enter="handleEnter"
|
||||
hide-details="auto"
|
||||
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>
|
||||
|
@ -23,7 +25,7 @@
|
|||
</v-text-field>
|
||||
</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">
|
||||
<template v-if="!loading">
|
||||
<v-list density="compact" class="list-container">
|
||||
|
@ -68,161 +70,128 @@
|
|||
|
||||
<v-dialog v-model="showAddDialog" :fullscreen="$vuetify.display.mobile" max-width="800">
|
||||
<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="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-row class="mt-4">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.name"
|
||||
label="نام کالا/خدمت *"
|
||||
required
|
||||
:error-messages="nameErrors"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.code"
|
||||
label="کد"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="newCommodity.type"
|
||||
:items="commodityTypes"
|
||||
label="نوع *"
|
||||
required
|
||||
:error-messages="typeErrors"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="newCommodity.unit"
|
||||
:items="units"
|
||||
label="واحد اندازهگیری *"
|
||||
required
|
||||
:error-messages="unitErrors"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="newCommodity.description"
|
||||
label="توضیحات"
|
||||
rows="3"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-window-item>
|
||||
|
||||
<v-window-item value="details">
|
||||
<v-row class="mt-4">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.brand"
|
||||
label="برند"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.model"
|
||||
label="مدل"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.barcode"
|
||||
label="بارکد"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.serial"
|
||||
label="سریال"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-switch
|
||||
v-model="newCommodity.isService"
|
||||
label="خدمت"
|
||||
color="primary"
|
||||
></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
|
||||
<v-window-item value="pricing">
|
||||
<v-row class="mt-4">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.basePrice"
|
||||
label="قیمت پایه"
|
||||
type="number"
|
||||
prefix="ریال"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.salePrice"
|
||||
label="قیمت فروش"
|
||||
type="number"
|
||||
prefix="ریال"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.minStock"
|
||||
label="حداقل موجودی"
|
||||
type="number"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.maxStock"
|
||||
label="حداکثر موجودی"
|
||||
type="number"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<v-card-title class="text-h5">
|
||||
افزودن کالا/خدمت جدید
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="saveCommodity">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.name"
|
||||
label="نام کالا/خدمت *"
|
||||
required
|
||||
:error-messages="nameErrors"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.code"
|
||||
label="کد"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="newCommodity.type"
|
||||
:items="commodityTypes"
|
||||
label="نوع *"
|
||||
required
|
||||
:error-messages="typeErrors"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="newCommodity.unit"
|
||||
:items="units"
|
||||
label="واحد اندازهگیری *"
|
||||
required
|
||||
:error-messages="unitErrors"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="newCommodity.description"
|
||||
label="توضیحات"
|
||||
rows="3"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.brand"
|
||||
label="برند"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.model"
|
||||
label="مدل"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.barcode"
|
||||
label="بارکد"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.serial"
|
||||
label="سریال"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-switch
|
||||
v-model="newCommodity.isService"
|
||||
label="خدمت"
|
||||
color="primary"
|
||||
></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.basePrice"
|
||||
label="قیمت پایه"
|
||||
type="number"
|
||||
prefix="ریال"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.salePrice"
|
||||
label="قیمت فروش"
|
||||
type="number"
|
||||
prefix="ریال"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.minStock"
|
||||
label="حداقل موجودی"
|
||||
type="number"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="newCommodity.maxStock"
|
||||
label="حداکثر موجودی"
|
||||
type="number"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</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-dialog>
|
||||
|
||||
|
@ -281,7 +250,6 @@ export default defineComponent({
|
|||
itemsPerPage: 10,
|
||||
searchTimeout: null,
|
||||
showAddDialog: false,
|
||||
tabs: 'basic',
|
||||
saving: false,
|
||||
commodityTypes: ['کالا', 'خدمت'],
|
||||
units: ['عدد', 'کیلوگرم', 'گرم', 'متر', 'سانتیمتر', 'لیتر', 'متر مربع', 'متر مکعب'],
|
||||
|
@ -349,10 +317,14 @@ export default defineComponent({
|
|||
watch: {
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
if (this.returnObject) {
|
||||
this.selectedItem = newVal
|
||||
if (newVal) {
|
||||
if (this.returnObject) {
|
||||
this.selectedItem = newVal;
|
||||
} else {
|
||||
this.selectedItem = this.items.find(item => item.id === newVal) || { id: newVal, name: 'در حال بارگذاری...' };
|
||||
}
|
||||
} else {
|
||||
this.selectedItem = this.items.find(item => item.id === newVal)
|
||||
this.selectedItem = null;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
|
@ -408,6 +380,9 @@ export default defineComponent({
|
|||
this.selectedItem = this.modelValue
|
||||
} else {
|
||||
this.selectedItem = this.items.find(item => item.id === this.modelValue)
|
||||
if (!this.selectedItem) {
|
||||
await this.fetchSingleCommodity(this.modelValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -418,6 +393,26 @@ export default defineComponent({
|
|||
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() {
|
||||
if (!this.newCommodity.name) {
|
||||
this.showMessage('نام کالا/خدمت الزامی است', 'error')
|
||||
|
@ -485,43 +480,4 @@ export default defineComponent({
|
|||
max-height: 300px;
|
||||
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>
|
|
@ -9,13 +9,15 @@
|
|||
:error-messages="errorMessages"
|
||||
:rules="combinedRules"
|
||||
:label="label"
|
||||
class=""
|
||||
class="my-0"
|
||||
prepend-inner-icon="mdi-account"
|
||||
clearable
|
||||
@click:clear="clearSelection"
|
||||
:loading="loading"
|
||||
@keydown.enter="handleEnter"
|
||||
hide-details="auto"
|
||||
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>
|
||||
|
@ -23,7 +25,7 @@
|
|||
</v-text-field>
|
||||
</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">
|
||||
<template v-if="!loading">
|
||||
<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}"
|
||||
},
|
||||
dialog: {
|
||||
change_password: 'تغییر کلمه عبور',
|
||||
download: 'دانلود',
|
||||
delete_group: 'حذف گروهی',
|
||||
add_new_transfer: 'سند انتقال جدید',
|
||||
|
|
|
@ -1,60 +1,152 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-toolbar
|
||||
color="toolbar"
|
||||
title="اسناد حسابداری"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||
<v-toolbar color="toolbar" title="اسناد حسابداری">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip :text="$t('dialog.back')" 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></v-spacer>
|
||||
<v-tooltip v-if="isPluginActive('accpro')" 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" />
|
||||
<v-btn v-bind="props" icon="mdi-plus" variant="text" color="success" :to="'/acc/accounting/mod'"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-toolbar>
|
||||
|
||||
<v-text-field
|
||||
v-model="searchValue"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
density="compact"
|
||||
hide-details
|
||||
:rounded="false"
|
||||
placeholder="جست و جو ..."
|
||||
></v-text-field>
|
||||
<v-text-field v-model="searchValue" prepend-inner-icon="mdi-magnify" density="compact" hide-details :rounded="false"
|
||||
placeholder="جست و جو ..."></v-text-field>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filteredItems"
|
||||
:search="searchValue"
|
||||
:loading="loading"
|
||||
:header-props="{ class: 'custom-header' }"
|
||||
hover
|
||||
>
|
||||
<v-data-table :headers="headers" :items="filteredItems" :search="searchValue" :loading="loading"
|
||||
:header-props="{ class: 'custom-header' }" hover>
|
||||
<template v-slot:item.state="{ item }">
|
||||
<v-icon
|
||||
:color="item.type !== 'accounting' ? 'error' : 'success'"
|
||||
>
|
||||
{{ item.type !== 'accounting' ? 'mdi-lock' : 'mdi-lock-open' }}
|
||||
<v-icon :color="item.type !== 'calc' ? 'error' : 'success'">
|
||||
{{ item.type !== 'calc' ? 'mdi-lock' : 'mdi-lock-open' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
|
||||
<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 }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
icon
|
||||
variant="text"
|
||||
color="success"
|
||||
:to="'/acc/accounting/view/' + item.code"
|
||||
>
|
||||
<v-btn v-bind="props" icon variant="text" color="success" :to="'/acc/accounting/view/' + item.code">
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</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>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -65,6 +157,15 @@ import axios from 'axios'
|
|||
const searchValue = ref('')
|
||||
const loading = ref(true)
|
||||
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 = [
|
||||
{ title: 'وضعیت', key: 'state', sortable: true },
|
||||
|
@ -76,6 +177,10 @@ const headers = [
|
|||
{ title: 'ثبت کننده', key: 'submitter', sortable: true }
|
||||
]
|
||||
|
||||
const isPluginActive = (plugName) => {
|
||||
return plugins.value[plugName] !== undefined
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
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(() => {
|
||||
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(() => {
|
||||
loadData()
|
||||
loadPlugins()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
<template>
|
||||
|
||||
<v-toolbar color="toolbar" :title="$t('dialog.accounting_doc')">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip :text="$t('dialog.back')" 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>
|
||||
<v-toolbar color="toolbar" :title="$t('dialog.accounting_doc')">
|
||||
<template v-slot:prepend>
|
||||
<v-tooltip :text="$t('dialog.back')" 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></v-spacer>
|
||||
<v-tooltip text="ثبت سند" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" variant="text" icon="mdi-content-save" color="success" @click="submitForm" :loading="loading"></v-btn>
|
||||
</template>
|
||||
<v-spacer></v-spacer>
|
||||
<v-tooltip text="ثبت سند" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" variant="text" icon="mdi-content-save" color="success" @click="submitForm" :loading="loading"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip v-if="docId" text="حذف سند" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" variant="text" icon="mdi-delete" color="error" @click="deleteDialog = true" :loading="loading"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-toolbar>
|
||||
</v-tooltip>
|
||||
<v-tooltip v-if="docId" text="حذف سند" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" variant="text" icon="mdi-delete" color="error" @click="deleteDialog = true" :loading="loading"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-toolbar>
|
||||
|
||||
<v-container>
|
||||
<v-form @submit.prevent="submitForm">
|
||||
|
@ -40,6 +39,13 @@
|
|||
</v-col>
|
||||
</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%;">
|
||||
<thead>
|
||||
<tr style="background-color: #0D47A1; color: white; height: 40px;">
|
||||
|
@ -52,130 +58,251 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(row, index) in form.rows" :key="index">
|
||||
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '40px' }">
|
||||
<td class="text-center" style="min-width: 150px; padding: 0 4px;">
|
||||
<Haccountsearch
|
||||
v-model="row.ref"
|
||||
:rules="[v => !!v || 'حساب الزامی است']"
|
||||
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center" style="min-width: 100px; padding: 0 4px;">
|
||||
</td>
|
||||
<td class="text-center" style="padding: 0 4px;">
|
||||
<v-text-field
|
||||
v-model="row.des"
|
||||
label="توضیحات"
|
||||
density="compact"
|
||||
class="my-0"
|
||||
style="font-size: 0.7rem;"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</td>
|
||||
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||
<v-text-field
|
||||
v-model="row.bd"
|
||||
label="بدهکار"
|
||||
type="number"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.7rem;"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</td>
|
||||
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||
<v-text-field
|
||||
v-model="row.bs"
|
||||
label="بستانکار"
|
||||
type="number"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.7rem;"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</td>
|
||||
<td class="text-center" style="width: 50px; padding: 0 4px;">
|
||||
<v-tooltip text="حذف" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-delete" variant="text" size="x-small" color="error"
|
||||
@click="removeRow(row)" style="min-width: 30px;"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<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">
|
||||
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '40px' }">
|
||||
<td class="text-center" style="min-width: 150px; padding: 0 4px;">
|
||||
<Haccountsearch
|
||||
v-model="row.ref"
|
||||
:rules="[v => !!v || 'حساب الزامی است']"
|
||||
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||
@tableType="(type) => handleTableType(row, type)"
|
||||
/>
|
||||
</td>
|
||||
<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 class="text-center" style="padding: 0 4px;">
|
||||
<v-text-field
|
||||
v-model="row.des"
|
||||
label="توضیحات"
|
||||
density="compact"
|
||||
class="my-0"
|
||||
style="font-size: 0.7rem;"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</td>
|
||||
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||
<Hnumberinput
|
||||
v-model="row.bd"
|
||||
label="بدهکار"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.7rem;"
|
||||
hide-details
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||
<Hnumberinput
|
||||
v-model="row.bs"
|
||||
label="بستانکار"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.7rem;"
|
||||
hide-details
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center" style="width: 50px; padding: 0 4px;">
|
||||
<v-tooltip text="حذف" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-delete" variant="text" size="x-small" color="error"
|
||||
@click="removeRow(row)" style="min-width: 30px;"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center pa-1" style="height: 40px;">
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" size="x-small" @click="addRow">افزودن سطر جدید</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center pa-1" style="height: 40px;">
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" size="x-small" @click="addRow">افزودن سطر جدید</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<!-- جدول موبایل -->
|
||||
<div class="d-sm-none">
|
||||
<v-card v-for="(row, index) in form.rows" :key="index" class="mb-4" variant="outlined">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-subtitle-2 font-weight-bold">ردیف:</span>
|
||||
<span>{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Haccountsearch
|
||||
v-model="row.ref"
|
||||
:rules="[v => !!v || 'حساب الزامی است']"
|
||||
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<v-text-field
|
||||
v-model="row.des"
|
||||
label="توضیحات"
|
||||
density="compact"
|
||||
class="my-0"
|
||||
style="font-size: 0.8rem;"
|
||||
></v-text-field>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between mb-2">
|
||||
<div style="width: 48%;">
|
||||
<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-text>
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-subtitle-2 font-weight-bold">ردیف:</span>
|
||||
<span>{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<Haccountsearch
|
||||
v-model="row.ref"
|
||||
:rules="[v => !!v || 'حساب الزامی است']"
|
||||
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||
/>
|
||||
</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">
|
||||
<v-text-field
|
||||
v-model="row.bd"
|
||||
label="بدهکار"
|
||||
type="number"
|
||||
v-model="row.des"
|
||||
label="توضیحات"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.8rem;"
|
||||
></v-text-field>
|
||||
</div>
|
||||
<div style="width: 48%;">
|
||||
<v-text-field
|
||||
v-model="row.bs"
|
||||
label="بستانکار"
|
||||
type="number"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.8rem;"
|
||||
></v-text-field>
|
||||
<div class="d-flex justify-space-between mb-2">
|
||||
<div style="width: 48%;">
|
||||
<Hnumberinput
|
||||
v-model="row.bd"
|
||||
label="بدهکار"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.8rem;"
|
||||
/>
|
||||
</div>
|
||||
<div style="width: 48%;">
|
||||
<Hnumberinput
|
||||
v-model="row.bs"
|
||||
label="بستانکار"
|
||||
density="compact"
|
||||
@input="calculateTotals"
|
||||
class="my-0"
|
||||
style="font-size: 0.8rem;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon="mdi-delete" variant="text" color="error" @click="removeRow(row)"></v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon="mdi-delete" variant="text" color="error" @click="removeRow(row)"></v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" block class="mb-4" @click="addRow">افزودن ردیف جدید</v-btn>
|
||||
</div>
|
||||
|
||||
<v-row class="mt-4">
|
||||
<v-col cols="6">
|
||||
<v-text-field
|
||||
:value="totalBd"
|
||||
v-model="totalBd"
|
||||
label="جمع بدهکار"
|
||||
readonly
|
||||
dense
|
||||
|
@ -183,15 +310,13 @@
|
|||
</v-col>
|
||||
<v-col cols="6">
|
||||
<v-text-field
|
||||
:value="totalBs"
|
||||
v-model="totalBs"
|
||||
label="جمع بستانکار"
|
||||
readonly
|
||||
dense
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-alert v-if="error" type="error" class="mt-4">{{ error }}</v-alert>
|
||||
</v-form>
|
||||
</v-container>
|
||||
|
||||
|
@ -215,6 +340,23 @@
|
|||
</v-card-actions>
|
||||
</v-card>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
|
@ -222,17 +364,23 @@ import axios from 'axios';
|
|||
import moment from 'jalali-moment';
|
||||
import Hdatepicker from '@/components/forms/Hdatepicker.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 {
|
||||
components: {
|
||||
Hdatepicker,
|
||||
Haccountsearch
|
||||
},
|
||||
props: {
|
||||
docId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
Haccountsearch,
|
||||
Hbankaccountsearch,
|
||||
Hcashdesksearch,
|
||||
Hsalarysearch,
|
||||
Hcommoditysearch,
|
||||
Hpersonsearch,
|
||||
Hnumberinput
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -240,54 +388,117 @@ export default {
|
|||
date: '',
|
||||
des: '',
|
||||
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,
|
||||
totalBs: 0,
|
||||
error: null,
|
||||
deleteDialog: false,
|
||||
loading: false,
|
||||
snackbar: {
|
||||
show: false,
|
||||
text: '',
|
||||
color: 'success'
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchHesabdariTables();
|
||||
if (this.docId) {
|
||||
this.fetchDoc();
|
||||
computed: {
|
||||
docId() {
|
||||
return this.$route.params.id;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
Promise.all([
|
||||
this.docId ? this.fetchDoc() : Promise.resolve()
|
||||
]).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async fetchHesabdariTables() {
|
||||
try {
|
||||
const response = await axios.get('/api/hesabdari/tables');
|
||||
this.hesabdariTables = response.data.data;
|
||||
} catch (error) {
|
||||
console.error('خطا در دریافت حسابها:', error.response?.data || error.message);
|
||||
this.error = 'خطا در بارگذاری حسابها: ' + (error.response?.data?.message || 'مشکل ناشناخته');
|
||||
}
|
||||
showSnackbar(text, color = 'success') {
|
||||
this.snackbar.text = text;
|
||||
this.snackbar.color = color;
|
||||
this.snackbar.show = true;
|
||||
},
|
||||
async fetchDoc() {
|
||||
try {
|
||||
const response = await axios.get(`/api/hesabdari/doc/${this.docId}`);
|
||||
const serverDate = response.data.data.date;
|
||||
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY-MM-DD');
|
||||
this.form.des = response.data.data.des || '';
|
||||
this.form.rows = response.data.data.rows.map(row => ({
|
||||
ref: row.ref.id,
|
||||
refName: row.ref.name,
|
||||
bd: row.bd,
|
||||
bs: row.bs,
|
||||
des: row.des,
|
||||
selectedAccounts: [{ id: row.ref.id, name: row.ref.name }],
|
||||
}));
|
||||
this.calculateTotals();
|
||||
const response = await axios.get(`/api/hesabdari/direct/doc/get/${this.docId}`);
|
||||
if (response.data.success) {
|
||||
const serverDate = response.data.data.date;
|
||||
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY/MM/DD');
|
||||
this.form.des = response.data.data.des || '';
|
||||
|
||||
// ایجاد یک آرایه موقت برای ذخیره ردیفها
|
||||
const tempRows = response.data.data.rows.map(row => ({
|
||||
ref: row.ref.id,
|
||||
refName: row.ref.name,
|
||||
bd: row.bd,
|
||||
bs: row.bs,
|
||||
des: row.des,
|
||||
detail: row.detail || '',
|
||||
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();
|
||||
} else {
|
||||
this.error = response.data.message || 'خطا در بارگذاری سند';
|
||||
}
|
||||
} catch (error) {
|
||||
this.error = 'خطا در بارگذاری سند: ' + (error.response?.data?.message || 'مشکل ناشناخته');
|
||||
}
|
||||
},
|
||||
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) {
|
||||
const index = this.form.rows.indexOf(item);
|
||||
|
@ -297,46 +508,180 @@ export default {
|
|||
}
|
||||
},
|
||||
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.totalBs = this.form.rows.reduce((sum, row) => sum + parseInt(row.bs || 0), 0);
|
||||
},
|
||||
selectAccount(row, selected) {
|
||||
if (selected.length > 0) {
|
||||
const account = selected[0];
|
||||
row.ref = account.id;
|
||||
row.refName = account.name;
|
||||
row.selectedAccounts = [account];
|
||||
validateDebitCredit(row) {
|
||||
if (parseInt(row.bd) > 0 && parseInt(row.bs) > 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.refName = account.name;
|
||||
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() {
|
||||
this.error = null;
|
||||
if (this.form.rows.length < 2) {
|
||||
this.error = 'حداقل باید دو سطر در سند وجود داشته باشد';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.totalBd !== this.totalBs) {
|
||||
this.error = 'جمع بدهکار و بستانکار باید برابر باشد';
|
||||
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 = {
|
||||
date: moment(this.form.date, 'YYYY-MM-DD').locale('fa').format('YYYY/MM/DD'),
|
||||
date: this.form.date,
|
||||
des: this.form.des,
|
||||
rows: this.form.rows.map(row => ({
|
||||
ref: row.ref,
|
||||
bd: row.bd,
|
||||
bs: row.bs,
|
||||
des: row.des,
|
||||
detail: row.detail,
|
||||
bankAccount: row.bankAccount,
|
||||
cashdesk: row.cashdesk,
|
||||
salary: row.salary,
|
||||
commodity: row.commodity,
|
||||
commodityCount: row.commodityCount,
|
||||
person: row.person
|
||||
})),
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
this.loading = true;
|
||||
let response;
|
||||
if (this.docId) {
|
||||
await axios.put(`/api/hesabdari/doc/${this.docId}`, payload);
|
||||
this.$emit('saved', 'سند با موفقیت ویرایش شد');
|
||||
response = await axios.put(`/api/hesabdari/direct/doc/update/${this.docId}`, payload);
|
||||
} else {
|
||||
const response = await axios.post('/api/hesabdari/doc', payload);
|
||||
this.$emit('saved', 'سند با موفقیت ثبت شد', response.data.data.id);
|
||||
response = await axios.post('/api/hesabdari/direct/doc/create', payload);
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
this.loading = false;
|
||||
}
|
||||
|
@ -344,10 +689,17 @@ export default {
|
|||
async confirmDelete() {
|
||||
try {
|
||||
this.loading = true;
|
||||
await axios.delete(`/api/hesabdari/doc/${this.docId}`);
|
||||
this.$router.push('/acc/accounting/list');
|
||||
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');
|
||||
}, 1000);
|
||||
} else {
|
||||
this.error = response?.data?.message || 'خطا در حذف سند';
|
||||
}
|
||||
} catch (error) {
|
||||
this.error = 'خطا در حذف سند';
|
||||
this.error = error.response?.data?.message || 'خطا در حذف سند';
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
|
|
|
@ -1,249 +1,400 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from "axios";
|
||||
import Loading from "vue-loading-overlay";
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
||||
<template>
|
||||
<v-toolbar color="toolbar" title="حواله ورود به انبار">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
<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({
|
||||
name: "buy",
|
||||
components: {
|
||||
Loading,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
loading: false,
|
||||
doc: {},
|
||||
ticket: {
|
||||
type: 'input',
|
||||
typeString: 'حواله ورود',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {},
|
||||
person: {},
|
||||
transferType: {},
|
||||
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,
|
||||
},
|
||||
<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;
|
||||
}
|
||||
|
||||
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',
|
||||
typeString: 'حواله ورود',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {} as Store,
|
||||
person: {} as Person,
|
||||
transferType: {} as TransferType,
|
||||
referral: ''
|
||||
})
|
||||
|
||||
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('تعداد تمام کالاها صفر است!')
|
||||
}
|
||||
},
|
||||
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) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: errors.join('\n'),
|
||||
color: 'error'
|
||||
}
|
||||
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;
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||
doc: doc.value,
|
||||
ticket: {
|
||||
...ticket.value,
|
||||
senderTel: ticket.value.person.mobile || '',
|
||||
sms: false
|
||||
},
|
||||
items: items.value
|
||||
})
|
||||
|
||||
if (response.data.result === 0) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||
color: 'success'
|
||||
}
|
||||
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 + 'مورد تحویل شد. ';
|
||||
})
|
||||
},
|
||||
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)) {
|
||||
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();
|
||||
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)) {
|
||||
evt.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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-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>
|
||||
<style scoped>
|
||||
.v-data-table {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
|
@ -1,257 +1,455 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent, ref} from 'vue'
|
||||
import recList from "../../component/recList.vue";
|
||||
import axios from "axios";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
export default defineComponent({
|
||||
name: "modalNew",
|
||||
components: {},
|
||||
watch:{
|
||||
'item.type'(newValue,oldValue) {
|
||||
if (newValue == 'sell') { this.$data.item.title = 'فروش'; this.$data.item.removeBeforeTicketsEnable = true; }
|
||||
else if (newValue == 'buy') { this.$data.item.title = 'خرید'; this.$data.item.removeBeforeTicketsEnable = true; }
|
||||
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; }
|
||||
else if (newValue == 'wastage') {
|
||||
this.$data.item.title = 'ضایعات';
|
||||
this.$data.item.removeBeforeTicketsEnable = false;
|
||||
this.$data.item.removeBeforeTickets = false;
|
||||
}
|
||||
else if (newValue == 'used') {
|
||||
this.$data.item.title = 'مصرف مستقیم';
|
||||
this.$data.item.removeBeforeTicketsEnable = false;
|
||||
this.$data.item.removeBeforeTickets = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
loading: ref(false),
|
||||
storerooms: [],
|
||||
item: {
|
||||
storeroom: null,
|
||||
type: 'sell',
|
||||
title: 'فروش',
|
||||
docSell: null,
|
||||
docBuy: null,
|
||||
removeBeforeTickets: 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() {
|
||||
this.loading = true;
|
||||
if (this.item.storeroom == null) {
|
||||
Swal.fire({
|
||||
text: 'انبار انتخاب نشده است.',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
}).then((res) => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
else if (this.item.type == 'sell' && this.item.docSell == null) {
|
||||
Swal.fire({
|
||||
text: 'فاکتور فروش انتخاب نشده است.',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
}).then((res) => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
else if (this.item.type == 'buy' && this.item.docBuy == null) {
|
||||
Swal.fire({
|
||||
text: 'فاکتور خرید انتخاب نشده است.',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
}).then((res) => {
|
||||
this.loading = 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();
|
||||
}
|
||||
})
|
||||
</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>
|
||||
<v-toolbar color="toolbar" title="حواله انبار جدید">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
<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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<!-- ستون سمت راست - انتخاب انبار -->
|
||||
<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-warehouse</v-icon>
|
||||
انتخاب انبار
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-select
|
||||
v-model="item.storeroom"
|
||||
: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>
|
||||
|
||||
<style scoped>
|
||||
<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;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
code: string;
|
||||
des: string;
|
||||
}
|
||||
|
||||
interface Item {
|
||||
storeroom: Storeroom | null;
|
||||
type: 'sell' | 'buy' | 'rfbuy' | 'rfsell' | 'wastage' | 'used';
|
||||
title: string;
|
||||
docSell: Document | null;
|
||||
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,
|
||||
type: 'sell',
|
||||
title: 'فروش',
|
||||
docSell: null,
|
||||
docBuy: null,
|
||||
docRfsell: null,
|
||||
docRfbuy: null,
|
||||
removeBeforeTickets: true,
|
||||
removeBeforeTicketsEnable: true
|
||||
})
|
||||
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
message: '',
|
||||
color: 'primary' as 'primary' | 'error' | 'success' | 'warning'
|
||||
})
|
||||
|
||||
// Watchers
|
||||
watch(() => item.value.type, (newValue) => {
|
||||
if (newValue === 'sell') {
|
||||
item.value.title = 'فروش'
|
||||
item.value.removeBeforeTicketsEnable = true
|
||||
} else if (newValue === 'buy') {
|
||||
item.value.title = 'خرید'
|
||||
item.value.removeBeforeTicketsEnable = true
|
||||
} else if (newValue === 'rfbuy') {
|
||||
item.value.title = 'برگشت از خرید'
|
||||
item.value.removeBeforeTicketsEnable = true
|
||||
} else if (newValue === 'rfsell') {
|
||||
item.value.title = 'برگشت از فروش'
|
||||
item.value.removeBeforeTicketsEnable = true
|
||||
} else if (newValue === 'wastage') {
|
||||
item.value.title = 'ضایعات'
|
||||
item.value.removeBeforeTicketsEnable = false
|
||||
item.value.removeBeforeTickets = false
|
||||
} else if (newValue === 'used') {
|
||||
item.value.title = 'مصرف مستقیم'
|
||||
item.value.removeBeforeTicketsEnable = false
|
||||
item.value.removeBeforeTickets = false
|
||||
}
|
||||
})
|
||||
|
||||
// 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>
|
||||
|
||||
<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>
|
|
@ -1,249 +1,402 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from "axios";
|
||||
import Loading from "vue-loading-overlay";
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
||||
import Swal from "sweetalert2";
|
||||
<template>
|
||||
<v-toolbar color="toolbar" title="حواله خروج از انبار">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
<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({
|
||||
name: "rfbuy",
|
||||
components: {
|
||||
Loading,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
loading: false,
|
||||
doc: {},
|
||||
ticket: {
|
||||
type: 'output',
|
||||
typeString: 'حواله خروج',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {},
|
||||
person: {},
|
||||
transferType: {},
|
||||
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,
|
||||
},
|
||||
<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>
|
||||
|
||||
<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',
|
||||
typeString: 'حواله خروج',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {} as Store,
|
||||
person: {} as Person,
|
||||
transferType: {} as TransferType,
|
||||
referral: '',
|
||||
sms: false
|
||||
})
|
||||
|
||||
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('تعداد تمام کالاها صفر است!')
|
||||
}
|
||||
},
|
||||
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) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: errors.join('\n'),
|
||||
color: 'error'
|
||||
}
|
||||
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;
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||
doc: doc.value,
|
||||
ticket: {
|
||||
...ticket.value,
|
||||
senderTel: ticket.value.person.mobile || ''
|
||||
},
|
||||
items: items.value
|
||||
})
|
||||
|
||||
if (response.data.result === 0) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||
color: 'success'
|
||||
}
|
||||
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';
|
||||
})
|
||||
},
|
||||
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)) {
|
||||
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();
|
||||
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)) {
|
||||
evt.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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-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>
|
||||
<style scoped>
|
||||
.v-data-table {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
|
@ -1,249 +1,401 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from "axios";
|
||||
import Loading from "vue-loading-overlay";
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
||||
<template>
|
||||
<v-toolbar color="toolbar" title="حواله خروج از انبار">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
<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({
|
||||
name: "rfsell",
|
||||
components: {
|
||||
Loading,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
loading: false,
|
||||
doc: {},
|
||||
ticket: {
|
||||
type: 'input',
|
||||
typeString: 'حواله ورود',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {},
|
||||
person: {},
|
||||
transferType: {},
|
||||
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,
|
||||
},
|
||||
<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: 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',
|
||||
typeString: 'حواله ورود از انبار',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {} as Store,
|
||||
person: {} as Person,
|
||||
transferType: {} as TransferType,
|
||||
referral: '',
|
||||
sms: false
|
||||
})
|
||||
|
||||
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('تعداد تمام کالاها صفر است!')
|
||||
}
|
||||
},
|
||||
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) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: errors.join('\n'),
|
||||
color: 'error'
|
||||
}
|
||||
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;
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||
doc: doc.value,
|
||||
ticket: {
|
||||
...ticket.value,
|
||||
senderTel: ticket.value.person.mobile || ''
|
||||
},
|
||||
items: items.value
|
||||
})
|
||||
|
||||
if (response.data.result === 0) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||
color: 'success'
|
||||
}
|
||||
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 + 'مورد تحویل شد. ';
|
||||
})
|
||||
},
|
||||
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)) {
|
||||
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();
|
||||
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)) {
|
||||
evt.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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-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>
|
||||
<style scoped>
|
||||
.v-data-table {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
|
@ -1,282 +1,441 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from "axios";
|
||||
import Loading from "vue-loading-overlay";
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
import VuePersianDatetimePicker from 'vue-persian-datetime-picker';
|
||||
import Swal from "sweetalert2";
|
||||
<template>
|
||||
<v-toolbar color="toolbar" title="حواله خروج از انبار">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
<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({
|
||||
name: "sell",
|
||||
components: {
|
||||
Loading,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
loading: false,
|
||||
plugins: [],
|
||||
doc: {},
|
||||
ticket: {
|
||||
type: 'output',
|
||||
typeString: 'حواله خروج',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {},
|
||||
person: {},
|
||||
transferType: {},
|
||||
referral: '',
|
||||
sms: false,
|
||||
senderTel: 0
|
||||
},
|
||||
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,
|
||||
},
|
||||
<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>
|
||||
|
||||
<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',
|
||||
typeString: 'حواله خروج',
|
||||
date: '',
|
||||
des: '',
|
||||
transfer: '',
|
||||
receiver: '',
|
||||
code: '',
|
||||
store: {} as Store,
|
||||
person: {} as Person,
|
||||
transferType: {} as TransferType,
|
||||
referral: '',
|
||||
sms: false,
|
||||
senderTel: 0
|
||||
})
|
||||
|
||||
const transferTypes = ref<TransferType[]>([])
|
||||
const year = ref<Year>({} as Year)
|
||||
const items = ref<Commodity[]>([])
|
||||
const plugins = ref({})
|
||||
|
||||
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('تعداد تمام کالاها صفر است!')
|
||||
}
|
||||
},
|
||||
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) => {
|
||||
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'
|
||||
}
|
||||
},
|
||||
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';
|
||||
})
|
||||
},
|
||||
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)) {
|
||||
evt.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||
doc: doc.value,
|
||||
ticket: ticket.value,
|
||||
items: items.value
|
||||
})
|
||||
|
||||
if (response.data.result === 0) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: 'حواله انبار با موفقیت ثبت شد.',
|
||||
color: 'success'
|
||||
}
|
||||
},
|
||||
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();
|
||||
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)) {
|
||||
evt.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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-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>
|
||||
<style scoped>
|
||||
.v-data-table {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
|
@ -1,191 +1,358 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent,ref} from 'vue'
|
||||
import axios from "axios";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ticketList",
|
||||
data: ()=>{return {
|
||||
printID:'',
|
||||
loading : ref(false),
|
||||
inputItems:[],
|
||||
inputSearchValue: '',
|
||||
outputItems:[],
|
||||
outputSearchValue: '',
|
||||
headers: [
|
||||
{ text: "عملیات", value: "operation", width: "120" },
|
||||
{ text: "شماره", value: "code" },
|
||||
{ text: "تاریخ", value: "date", sortable: true },
|
||||
{ text: "شماره فاکتور", value: "doc.code", sortable: true, width: "100" },
|
||||
{ text: "شخص", value: "person.nikename", sortable: true, width: "120" },
|
||||
{ text: "توضیحات", value: "des", sortable: true, width: "300" },
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
axios.post('/api/storeroom/tickets/list/input')
|
||||
.then((response) => {
|
||||
this.inputItems = response.data;
|
||||
this.loading = false;
|
||||
});
|
||||
axios.post('/api/storeroom/tickets/list/output')
|
||||
.then((response) => {
|
||||
this.outputItems = response.data;
|
||||
this.loading = false;
|
||||
});
|
||||
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>
|
||||
|
||||
<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>
|
||||
<v-toolbar color="toolbar" title="حوالههای انبار">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
|
||||
<v-slide-group show-arrows>
|
||||
<v-slide-group-item>
|
||||
<v-tooltip text="حواله جدید" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" color="primary" icon="mdi-plus" :to="'/acc/storeroom/new/ticket/type'" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-slide-group-item>
|
||||
|
||||
<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-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>
|
||||
|
||||
<style scoped>
|
||||
<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;
|
||||
}
|
||||
|
||||
interface Header {
|
||||
title: string;
|
||||
key: string;
|
||||
align: string;
|
||||
sortable: boolean;
|
||||
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')
|
||||
]);
|
||||
inputItems.value = inputResponse.data;
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
|
||||
<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>
|
|
@ -1,216 +1,310 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent, ref} from 'vue'
|
||||
import axios from "axios";
|
||||
import Swal from "sweetalert2";
|
||||
export default defineComponent({
|
||||
name: "viewInvoice",
|
||||
components:{
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
},
|
||||
watch:{
|
||||
interface Business {
|
||||
legal_name: string
|
||||
}
|
||||
|
||||
},
|
||||
data:()=>{return{
|
||||
loading:ref(false),
|
||||
bid:{
|
||||
legal_name:'',
|
||||
},
|
||||
item:{
|
||||
ticket:{
|
||||
id:0,
|
||||
date:null,
|
||||
code:null,
|
||||
des:'',
|
||||
storeroom:{
|
||||
manager:''
|
||||
}
|
||||
},
|
||||
rows:[],
|
||||
person:{
|
||||
nikename: null,
|
||||
mobile:'',
|
||||
address:'',
|
||||
tel:'',
|
||||
codeeqtesadi:'',
|
||||
keshvar:'',
|
||||
ostan:'',
|
||||
shahr: ''
|
||||
},
|
||||
},
|
||||
headers: [
|
||||
{ text: "کالا", value: "commodity" },
|
||||
{ text: "تعداد", value: "count" },
|
||||
{ text: "مورد نیاز", value: "hesabdariCount" },
|
||||
{ text: "باقیمانده", value: "remain" },
|
||||
]
|
||||
interface Storeroom {
|
||||
manager: string
|
||||
}
|
||||
|
||||
interface Ticket {
|
||||
id: number
|
||||
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
|
||||
}
|
||||
},
|
||||
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');
|
||||
});
|
||||
}
|
||||
|
||||
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: {
|
||||
id: 0,
|
||||
date: null,
|
||||
code: null,
|
||||
des: '',
|
||||
type: '',
|
||||
typeString: '',
|
||||
storeroom: {
|
||||
manager: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
rows: [],
|
||||
person: {
|
||||
nikename: null,
|
||||
mobile: '',
|
||||
address: '',
|
||||
tel: '',
|
||||
codeeqtesadi: '',
|
||||
keshvar: '',
|
||||
ostan: '',
|
||||
shahr: '',
|
||||
postalcode: ''
|
||||
},
|
||||
transferType: null
|
||||
})
|
||||
|
||||
const headers = [
|
||||
{ title: "", key: "data-table-expand" },
|
||||
{ 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
|
||||
}
|
||||
}
|
||||
|
||||
const printInvoice = async () => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="block block-content-full">
|
||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light">
|
||||
<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="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 }}
|
||||
<v-toolbar color="toolbar" title="مشاهده و چاپ حواله انبار">
|
||||
<template v-slot:prepend>
|
||||
<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>
|
||||
<template #item-commodity="{ commodity }">
|
||||
{{ commodity.code }} {{ commodity.name }}
|
||||
</template>
|
||||
<template #expand="{ des, referal }">
|
||||
<div class="p-1 m-0 text-start">
|
||||
شرح
|
||||
:
|
||||
{{ des }}
|
||||
<br />
|
||||
ارجاع:
|
||||
{{ referal }}
|
||||
</div>
|
||||
</template>
|
||||
</EasyDataTable>
|
||||
</div>
|
||||
</div>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
<v-spacer />
|
||||
<v-tooltip text="چاپ" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
@click="printInvoice"
|
||||
color="primary"
|
||||
icon="mdi-printer"
|
||||
/>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-toolbar>
|
||||
|
||||
<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>
|
||||
|
||||
<style scoped>
|
||||
table{
|
||||
font-size: small;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
.table-header{
|
||||
background-color: lightgray;
|
||||
}
|
||||
.c-print{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media print
|
||||
{
|
||||
@media print {
|
||||
@page {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
body {
|
||||
body {
|
||||
padding-top: 72px;
|
||||
padding-bottom: 72px ;
|
||||
padding-bottom: 72px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,96 +1,314 @@
|
|||
<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-boxes-stacked"></i>
|
||||
انبارها </h3>
|
||||
<div class="block-options">
|
||||
<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
|
||||
<v-toolbar color="toolbar" title="انبارها">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
|
||||
:search-value="searchValue"
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
theme-color="#1d90ff"
|
||||
header-text-direction="center"
|
||||
body-text-direction="center"
|
||||
rowsPerPageMessage="تعداد سطر"
|
||||
emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
|
||||
rowsOfPageSeparatorMessage="از"
|
||||
:loading="loading"
|
||||
<v-slide-group show-arrows>
|
||||
<v-slide-group-item>
|
||||
<v-tooltip text="افزودن جدید" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-plus" color="primary" to="/acc/storeroom/mod/" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-slide-group-item>
|
||||
|
||||
<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"
|
||||
color="green"
|
||||
class="mb-0 pt-0 rounded-0"
|
||||
hide-details="auto"
|
||||
density="compact"
|
||||
:rounded="false"
|
||||
placeholder="جست و جو ..."
|
||||
clearable
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<v-tooltip location="bottom" text="جستجو">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props" color="danger" icon="mdi-magnify" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-data-table
|
||||
:headers="visibleHeaders"
|
||||
: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"
|
||||
>
|
||||
<template #item-operation="{ id }">
|
||||
<router-link :to="'/acc/storeroom/mod/' + id">
|
||||
<i class="fa fa-edit px-2"></i>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #item-active="{ active }">
|
||||
<label class="text-primary" v-if="active">فعال</label>
|
||||
<label class="text-danger" v-else>غیرفعال</label>
|
||||
</template>
|
||||
</EasyDataTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ 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>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Swal from "sweetalert2";
|
||||
import {ref} from "vue";
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: "list",
|
||||
data: ()=>{return {
|
||||
printID:'',
|
||||
searchValue: '',
|
||||
loading : ref(true),
|
||||
items:[],
|
||||
headers: [
|
||||
{ text: "کد", value: "id" },
|
||||
{ text: "نام انبار", value: "name", sortable: true},
|
||||
{ text: "انباردار", value: "manager", sortable: true},
|
||||
{ text: "تلفن", value: "tel", sortable: true},
|
||||
{ text: "آدرس", value: "adr"},
|
||||
{ text: "وضعیت", value: "active"},
|
||||
{ text: "عملیات", value: "operation"},
|
||||
]
|
||||
}},
|
||||
methods: {
|
||||
loadData(){
|
||||
axios.post('/api/storeroom/list/all')
|
||||
.then((response)=>{
|
||||
this.items = response.data.data;
|
||||
this.loading = false;
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.loadData();
|
||||
// Refs
|
||||
const loading = ref(false);
|
||||
const items = ref([]);
|
||||
const search = ref('');
|
||||
const showColumnDialog = ref(false);
|
||||
|
||||
// دیالوگها
|
||||
const deleteDialog = ref({
|
||||
show: false,
|
||||
id: null
|
||||
});
|
||||
|
||||
const messageDialog = ref({
|
||||
show: false,
|
||||
title: '',
|
||||
message: '',
|
||||
color: 'primary'
|
||||
});
|
||||
|
||||
// تابع فرمتکننده اعداد
|
||||
const formatNumber = (value) => {
|
||||
if (!value) return '0';
|
||||
return Number(value).toLocaleString('fa-IR');
|
||||
};
|
||||
|
||||
// تعریف همه ستونها
|
||||
const allHeaders = ref([
|
||||
{ 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>
|
||||
|
||||
<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>
|
|
@ -1,142 +1,195 @@
|
|||
<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 type="button" @click="$router.back()" class="btn text-warning mx-2 px-2">
|
||||
<i class="fa fw-bold fa-arrow-right"></i>
|
||||
</button>
|
||||
مشخصات انبار </h3>
|
||||
<div class="block-options">
|
||||
<button @click="save()" type="button" class="btn btn-sm btn-alt-primary">
|
||||
<i class="fa fa-save me-2"></i>
|
||||
ثبت
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-content py-3 vl-parent">
|
||||
<loading color="blue" loader="dots" v-model:active="isLoading" :is-full-page="false"/>
|
||||
<div class="container">
|
||||
<div class="row py-3">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div>
|
||||
<label class="me-4 text-primary">وضعیت انبار</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input v-model="this.data.active" class="form-check-input" type="radio" value="true">
|
||||
<label class="form-check-label" for="inlineCheckbox1">فعال</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input v-model="this.data.active" class="form-check-input" type="radio" value="false">
|
||||
<label class="form-check-label" for="inlineCheckbox2">غیرفعال</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="form-floating mb-4">
|
||||
<input v-model="data.name" class="form-control" type="text">
|
||||
<label class="form-label"><span class="text-danger">(لازم)</span> نام انبار</label>
|
||||
</div>
|
||||
<div class="form-floating mb-4">
|
||||
<input v-model="data.tel" class="form-control" type="text">
|
||||
<label class="form-label">تلفن</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="form-floating mb-4">
|
||||
<input v-model="data.manager" class="form-control" type="text">
|
||||
<label class="form-label">انباردار</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-floating mb-4">
|
||||
<input v-model="data.adr" class="form-control" type="text">
|
||||
<label class="form-label">آدرس</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-toolbar color="toolbar" title="مشخصات انبار">
|
||||
<template v-slot:prepend>
|
||||
<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 />
|
||||
<v-btn-toggle
|
||||
v-model="data.active"
|
||||
mandatory
|
||||
density="compact"
|
||||
class="mx-2"
|
||||
>
|
||||
<v-btn
|
||||
:value="true"
|
||||
size="small"
|
||||
:class="data.active ? 'bg-success' : ''"
|
||||
>
|
||||
<v-icon size="small" start>mdi-check-circle</v-icon>
|
||||
فعال
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:value="false"
|
||||
size="small"
|
||||
:class="!data.active ? 'bg-error' : ''"
|
||||
>
|
||||
<v-icon size="small" start>mdi-close-circle</v-icon>
|
||||
غیرفعال
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-tooltip text="ثبت" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" color="primary" icon="mdi-content-save" @click="save" :loading="isLoading" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-toolbar>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="data.name"
|
||||
label="نام انبار"
|
||||
:rules="[v => !!v || 'نام انبار الزامی است']"
|
||||
required
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
>
|
||||
<template v-slot:label>
|
||||
<span class="text-danger">(لازم)</span> نام انبار
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
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>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Swal from "sweetalert2";
|
||||
import Loading from 'vue-loading-overlay';
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
import {Money3} from "v-money3";
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: "mod",
|
||||
components: {
|
||||
Loading,
|
||||
Money3
|
||||
},
|
||||
data: ()=>{return{
|
||||
isLoading: false,
|
||||
units:'',
|
||||
data: {
|
||||
id:0,
|
||||
name: '',
|
||||
manager: '',
|
||||
active: true,
|
||||
tel: '',
|
||||
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;
|
||||
});
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
save() {
|
||||
if (this.data.name.length === 0)
|
||||
Swal.fire({
|
||||
text: 'نام کالا یا خدمات الزامی است.',
|
||||
icon: 'error',
|
||||
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')
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
}
|
||||
// Refs
|
||||
const isLoading = ref(false);
|
||||
const data = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
manager: '',
|
||||
active: true,
|
||||
tel: '',
|
||||
adr: '',
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ذخیره دادهها
|
||||
const save = async () => {
|
||||
if (!data.value.name) {
|
||||
showMessage('نام انبار الزامی است.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
|
||||
<style>
|
||||
.v-radio-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue