some bug fix
This commit is contained in:
parent
b8ad5f2f49
commit
29e755fd2c
|
|
@ -167,7 +167,7 @@ class AccountingDocService
|
|||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$salary)
|
||||
return ['error' => 'حقوق یافت نشد'];
|
||||
return ['error' => 'تنخواه یافت نشد'];
|
||||
|
||||
// Check if we should include preview documents
|
||||
$includePreview = $params['includePreview'] ?? false;
|
||||
|
|
|
|||
|
|
@ -441,18 +441,23 @@ class BankController extends AbstractController
|
|||
'توضیحات',
|
||||
'شرح سند',
|
||||
'تفصیل',
|
||||
'طرف حسابها',
|
||||
'بستانکار',
|
||||
'بدهکار',
|
||||
'سال مالی',
|
||||
]
|
||||
];
|
||||
foreach ($transactions as $transaction) {
|
||||
// استخراج طرف حسابها برای این تراکنش
|
||||
$counterpartAccounts = $this->getCounterpartAccountsForTransaction($transaction, $bank, $entityManager);
|
||||
|
||||
$arrayEntity[] = [
|
||||
$transaction->getId(),
|
||||
$transaction->getDoc()->getDate(),
|
||||
$transaction->getDes(),
|
||||
$transaction->getDoc()->getDes(),
|
||||
$transaction->getRef()->getName(),
|
||||
$counterpartAccounts,
|
||||
$transaction->getBs(),
|
||||
$transaction->getBd(),
|
||||
$transaction->getYear()->getlabel()
|
||||
|
|
@ -466,6 +471,57 @@ class BankController extends AbstractController
|
|||
return new BinaryFileResponse($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج طرف حسابهای مربوط به یک تراکنش
|
||||
*/
|
||||
private function getCounterpartAccountsForTransaction($transaction, $bank, EntityManagerInterface $entityManager): string
|
||||
{
|
||||
$doc = $transaction->getDoc();
|
||||
$bankCode = $bank->getCode();
|
||||
|
||||
// دریافت تمام ردیفهای مربوط به این سند
|
||||
$docRows = $entityManager->getRepository(HesabdariRow::class)
|
||||
->createQueryBuilder('hr')
|
||||
->leftJoin('hr.bank', 'ba')
|
||||
->leftJoin('hr.cashdesk', 'cd')
|
||||
->leftJoin('hr.salary', 's')
|
||||
->leftJoin('hr.person', 'p')
|
||||
->where('hr.doc = :doc')
|
||||
->setParameter('doc', $doc)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$accounts = [];
|
||||
foreach ($docRows as $docRow) {
|
||||
// بررسی اینکه آیا این ردیف طرف حساب است (نه بانک انتخابی)
|
||||
$isCounterpart = false;
|
||||
$accountName = '';
|
||||
|
||||
if ($docRow->getBank()) {
|
||||
if ($docRow->getBank()->getCode() != $bankCode) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'بانک: ' . $docRow->getBank()->getName();
|
||||
}
|
||||
} elseif ($docRow->getCashdesk()) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'صندوق: ' . $docRow->getCashdesk()->getName();
|
||||
} elseif ($docRow->getSalary()) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'تنخواه: ' . $docRow->getSalary()->getName();
|
||||
} elseif ($docRow->getPerson()) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'شخص: ' . $docRow->getPerson()->getNikename();
|
||||
}
|
||||
|
||||
if ($isCounterpart) {
|
||||
$amount = $docRow->getBd() > 0 ? $docRow->getBd() : $docRow->getBs();
|
||||
$accounts[] = $accountName . ' (' . number_format($amount, 0, '.', ',') . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return implode(' | ', $accounts);
|
||||
}
|
||||
|
||||
#[Route('/api/bank/card/list/print', name: 'app_bank_card_list_print')]
|
||||
public function app_bank_card_list_print(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
|
|
@ -521,6 +577,12 @@ class BankController extends AbstractController
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// اضافه کردن طرف حسابها به هر تراکنش
|
||||
foreach ($transactions as $transaction) {
|
||||
$transaction->counterpartAccounts = $this->getCounterpartAccountsForTransaction($transaction, $bank, $entityManager);
|
||||
}
|
||||
|
||||
$pid = $provider->createPrint(
|
||||
$acc['bid'],
|
||||
$this->getUser(),
|
||||
|
|
|
|||
|
|
@ -491,18 +491,28 @@ class HesabdariController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
// Set approval status based on business settings
|
||||
// وضعیت تایید: اگر autoApprove=true ارسال شده باشد، اجباری تایید شود
|
||||
$autoApprove = isset($params['autoApprove']) ? (bool)$params['autoApprove'] : null;
|
||||
$business = $acc['bid'];
|
||||
if ($business->isRequireTwoStepApproval()) {
|
||||
// Two-step approval is enabled
|
||||
if ($autoApprove === true) {
|
||||
$doc->setIsPreview(false);
|
||||
$doc->setIsApproved(true);
|
||||
$doc->setApprovedBy($this->getUser());
|
||||
} elseif ($autoApprove === false) {
|
||||
$doc->setIsPreview(true);
|
||||
$doc->setIsApproved(false);
|
||||
$doc->setApprovedBy(null);
|
||||
} else {
|
||||
// Two-step approval is disabled - auto approve
|
||||
$doc->setIsPreview(false);
|
||||
$doc->setIsApproved(true);
|
||||
$doc->setApprovedBy($this->getUser());
|
||||
// پیشفرض مطابق تنظیمات کسبوکار
|
||||
if ($business->isRequireTwoStepApproval()) {
|
||||
$doc->setIsPreview(true);
|
||||
$doc->setIsApproved(false);
|
||||
$doc->setApprovedBy(null);
|
||||
} else {
|
||||
$doc->setIsPreview(false);
|
||||
$doc->setIsApproved(true);
|
||||
$doc->setApprovedBy($this->getUser());
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('refData', $params))
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use App\Entity\Storeroom;
|
|||
use App\Entity\StoreroomItem;
|
||||
use App\Entity\StoreroomTicket;
|
||||
use App\Service\Explore;
|
||||
use App\Cog\AccountingDocService;
|
||||
use App\Cog\PersonService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
|
@ -2926,4 +2927,123 @@ class PersonsController extends AbstractController
|
|||
return $this->json(['success' => true, 'message' => "$successCount سند پرداخت تایید شد"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت طرف حسابهای مربوط به حساب بانکی
|
||||
*/
|
||||
#[Route('/api/person/bank/accounts/list', name: 'app_person_bank_accounts_list')]
|
||||
public function app_bank_accounts_list(
|
||||
Request $request,
|
||||
Access $access,
|
||||
EntityManagerInterface $entityManager,
|
||||
AccountingDocService $accountingDocService
|
||||
): JsonResponse {
|
||||
$acc = $access->hasRole('getpay');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$bankCode = $params['bankCode'] ?? null;
|
||||
|
||||
if (!$bankCode) {
|
||||
return $this->json(['error' => 'کد بانک الزامی است'], 400);
|
||||
}
|
||||
|
||||
// استفاده از AccountingDocService برای دریافت ردیفهای حسابداری
|
||||
$searchParams = [
|
||||
'type' => 'bank',
|
||||
'id' => $bankCode,
|
||||
'includePreview' => true // شامل اسناد پیشنمایش هم میشود
|
||||
];
|
||||
|
||||
$rows = $accountingDocService->searchRows($searchParams, $acc);
|
||||
|
||||
if (isset($rows['error'])) {
|
||||
return $this->json(['error' => $rows['error']], 400);
|
||||
}
|
||||
|
||||
// استخراج طرف حسابها از ردیفها
|
||||
$accounts = [];
|
||||
$processedDocs = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$docId = $row['code']; // کد سند
|
||||
|
||||
// اگر این سند قبلاً پردازش شده، رد کن
|
||||
if (in_array($docId, $processedDocs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$processedDocs[] = $docId;
|
||||
|
||||
// دریافت سند و ردیفهای مربوط به آن
|
||||
$doc = $entityManager->getRepository(\App\Entity\HesabdariDoc::class)->findOneBy(['code' => $docId]);
|
||||
if (!$doc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docRows = $entityManager->getRepository(\App\Entity\HesabdariRow::class)
|
||||
->createQueryBuilder('hr')
|
||||
->leftJoin('hr.bank', 'ba')
|
||||
->leftJoin('hr.cashdesk', 'cd')
|
||||
->leftJoin('hr.salary', 's')
|
||||
->leftJoin('hr.person', 'p')
|
||||
->where('hr.doc = :doc')
|
||||
->setParameter('doc', $doc)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$docAccounts = [];
|
||||
foreach ($docRows as $docRow) {
|
||||
// بررسی اینکه آیا این ردیف طرف حساب است (نه بانک انتخابی)
|
||||
$isCounterpart = false;
|
||||
$accountName = '';
|
||||
$accountType = '';
|
||||
$amount = 0;
|
||||
|
||||
if ($docRow->getBank()) {
|
||||
if ($docRow->getBank()->getCode() != $bankCode) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'بانک: ' . $docRow->getBank()->getName();
|
||||
$accountType = 'bank';
|
||||
}
|
||||
} elseif ($docRow->getCashdesk()) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'صندوق: ' . $docRow->getCashdesk()->getName();
|
||||
$accountType = 'cashdesk';
|
||||
} elseif ($docRow->getSalary()) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'تنخواه: ' . $docRow->getSalary()->getName();
|
||||
$accountType = 'salary';
|
||||
} elseif ($docRow->getPerson()) {
|
||||
$isCounterpart = true;
|
||||
$accountName = 'شخص: ' . $docRow->getPerson()->getNikename();
|
||||
$accountType = 'person';
|
||||
}
|
||||
|
||||
if ($isCounterpart) {
|
||||
$amount = $docRow->getBd() > 0 ? $docRow->getBd() : $docRow->getBs();
|
||||
$docAccounts[] = [
|
||||
'name' => $accountName,
|
||||
'type' => $accountType,
|
||||
'amount' => $amount,
|
||||
'formattedAmount' => number_format($amount, 0, '.', ','),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($docAccounts)) {
|
||||
$accounts[] = [
|
||||
'docId' => $docId,
|
||||
'date' => $row['date'],
|
||||
'type' => $row['type'],
|
||||
'des' => $row['des'],
|
||||
'accounts' => $docAccounts
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json($accounts);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use App\Entity\BankAccount;
|
||||
use App\Entity\Cashdesk;
|
||||
|
|
@ -800,11 +801,56 @@ class SellController extends AbstractController
|
|||
// اولویت با پارامترهای ارسالی است
|
||||
$printOptions = array_merge($defaultOptions, $params['printOptions'] ?? []);
|
||||
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
$docRepo = $entityManager->getRepository(HesabdariDoc::class);
|
||||
$doc = $docRepo->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
// Fallback در صورت ناهمخوانی activeMoney
|
||||
if (!$doc) {
|
||||
$doc = $docRepo->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code'],
|
||||
'year' => $acc['year']
|
||||
]);
|
||||
}
|
||||
// Fallback با QueryBuilder بر اساس شناسه BID (عدم تکیه بر شیء)
|
||||
if (!$doc) {
|
||||
try {
|
||||
$doc = $entityManager->createQueryBuilder()
|
||||
->select('d')
|
||||
->from(\App\Entity\HesabdariDoc::class, 'd')
|
||||
->where('d.code = :code')
|
||||
->andWhere('IDENTITY(d.bid) = :bidId')
|
||||
->setParameter('code', (string)($params['code'] ?? ''))
|
||||
->setParameter('bidId', $acc['bid']->getId())
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
} catch (\Exception $e) {
|
||||
$doc = null;
|
||||
}
|
||||
}
|
||||
// Fallback نهایی فقط بر اساس کد (در صورت وجود چند کسبوکار، کنترل امنیت بعدی برقرار است)
|
||||
if (!$doc) {
|
||||
try {
|
||||
$doc = $entityManager->createQueryBuilder()
|
||||
->select('d')
|
||||
->from(\App\Entity\HesabdariDoc::class, 'd')
|
||||
->where('d.code = :code')
|
||||
->setParameter('code', (string)($params['code'] ?? ''))
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
if ($doc && $doc->getBid()->getId() !== $acc['bid']->getId()) {
|
||||
// سند پیدا شد ولی متعلق به کسبوکار دیگری است
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$doc = null;
|
||||
}
|
||||
}
|
||||
if (!$doc)
|
||||
throw $this->createNotFoundException();
|
||||
$person = null;
|
||||
|
|
@ -953,6 +999,10 @@ class SellController extends AbstractController
|
|||
false,
|
||||
$printOptions['paper']
|
||||
);
|
||||
// اگر چاپ ابری انتخاب شده، فایل را به صف پرینترها اضافه کن
|
||||
if (!empty($params['printers'])) {
|
||||
$printers->addFile($pdfPid, $acc, "fastSellInvoice");
|
||||
}
|
||||
}
|
||||
if ($params['posPrint'] == true) {
|
||||
$pid = $provider->createPrint(
|
||||
|
|
@ -977,9 +1027,23 @@ class SellController extends AbstractController
|
|||
'showPercentDiscount' => $doc->getDiscountType() === 'percent',
|
||||
'discountPercent' => $doc->getDiscountPercent()
|
||||
]),
|
||||
false
|
||||
true
|
||||
);
|
||||
$printers->addFile($pid, $acc, "fastSellInvoice");
|
||||
$printers->addFile($pid, $acc, "fastSellPosInvoice");
|
||||
}
|
||||
// چاپ قبض صندوق در صورت نیاز
|
||||
if (!empty($params['posPrintRecp'])) {
|
||||
$pid = $provider->createPrint(
|
||||
$acc['bid'],
|
||||
$this->getUser(),
|
||||
$this->renderView('pdf/posPrinters/cashdesk.html.twig', [
|
||||
'bid' => $acc['bid'],
|
||||
'doc' => $doc,
|
||||
'rows' => $doc->getHesabdariRows(),
|
||||
]),
|
||||
true
|
||||
);
|
||||
$printers->addFile($pid, $acc, "fastSellCashdesk");
|
||||
}
|
||||
return $this->json(['id' => $pdfPid]);
|
||||
}
|
||||
|
|
@ -1587,4 +1651,452 @@ class SellController extends AbstractController
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/sell/list/excel', name: 'app_sell_list_excel', methods: ['POST'])]
|
||||
public function app_sell_list_excel(
|
||||
Provider $provider,
|
||||
Request $request,
|
||||
Access $access,
|
||||
Log $log,
|
||||
EntityManagerInterface $entityManager,
|
||||
Jdate $jdate
|
||||
): BinaryFileResponse|JsonResponse|StreamedResponse {
|
||||
$acc = $access->hasRole('sell');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$searchTerm = $params['search'] ?? '';
|
||||
$types = $params['types'] ?? [];
|
||||
$dateFilter = $params['dateFilter'] ?? 'all';
|
||||
$sortBy = $params['sortBy'] ?? [];
|
||||
|
||||
$queryBuilder = $entityManager->createQueryBuilder()
|
||||
->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount')
|
||||
->addSelect('d.isPreview, d.isApproved')
|
||||
->addSelect('u.fullName as submitter')
|
||||
->addSelect('approver.fullName as approvedByName, approver.id as approvedById, approver.email as approvedByEmail')
|
||||
->addSelect('l.code as labelCode, l.label as labelLabel')
|
||||
->from(HesabdariDoc::class, 'd')
|
||||
->leftJoin('d.submitter', 'u')
|
||||
->leftJoin('d.approvedBy', 'approver')
|
||||
->leftJoin('d.InvoiceLabel', 'l')
|
||||
->leftJoin('d.hesabdariRows', 'r')
|
||||
->where('d.bid = :bid')
|
||||
->andWhere('d.year = :year')
|
||||
->andWhere('d.type = :type')
|
||||
->andWhere('d.money = :money')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('year', $acc['year'])
|
||||
->setParameter('type', 'sell')
|
||||
->setParameter('money', $acc['money']);
|
||||
|
||||
// اعمال فیلترهای تاریخ
|
||||
$today = $jdate->jdate('Y/m/d', time());
|
||||
if ($dateFilter === 'today') {
|
||||
$queryBuilder->andWhere('d.date = :today')
|
||||
->setParameter('today', $today);
|
||||
} elseif ($dateFilter === 'week') {
|
||||
$weekStart = $jdate->jdate('Y/m/d', strtotime('-6 days'));
|
||||
$queryBuilder->andWhere('d.date BETWEEN :weekStart AND :today')
|
||||
->setParameter('weekStart', $weekStart)
|
||||
->setParameter('today', $today);
|
||||
} elseif ($dateFilter === 'month') {
|
||||
$monthStart = $jdate->jdate('Y/m/01', time());
|
||||
$queryBuilder->andWhere('d.date BETWEEN :monthStart AND :today')
|
||||
->setParameter('monthStart', $monthStart)
|
||||
->setParameter('today', $today);
|
||||
}
|
||||
|
||||
if ($searchTerm) {
|
||||
$queryBuilder->leftJoin('r.person', 'p')
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->orX(
|
||||
'd.code LIKE :search',
|
||||
'd.des LIKE :search',
|
||||
'd.date LIKE :search',
|
||||
'd.amount LIKE :search',
|
||||
'p.nikename LIKE :search',
|
||||
'p.mobile LIKE :search'
|
||||
)
|
||||
)
|
||||
->setParameter('search', "%$searchTerm%");
|
||||
}
|
||||
|
||||
if (!empty($types)) {
|
||||
$queryBuilder->andWhere('l.code IN (:types)')
|
||||
->setParameter('types', $types);
|
||||
}
|
||||
|
||||
// اگر آیتمهای خاصی درخواست شدهاند
|
||||
if (array_key_exists('items', $params)) {
|
||||
$codes = array_map(function($item) { return $item['code']; }, $params['items']);
|
||||
$queryBuilder->andWhere('d.code IN (:codes)')
|
||||
->setParameter('codes', $codes);
|
||||
}
|
||||
|
||||
// اعمال مرتبسازی
|
||||
if (!empty($sortBy)) {
|
||||
foreach ($sortBy as $sort) {
|
||||
$key = $sort['key'] ?? 'id';
|
||||
$direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? 'DESC' : 'ASC';
|
||||
if ($key === 'profit' || $key === 'receivedAmount') {
|
||||
continue; // اینها توی PHP مرتب میشن
|
||||
} elseif (in_array($key, ['id', 'dateSubmit', 'date', 'type', 'code', 'des', 'amount', 'isPreview', 'isApproved'])) {
|
||||
$queryBuilder->addOrderBy('d.' . $key, $direction);
|
||||
} elseif ($key === 'submitter') {
|
||||
$queryBuilder->addOrderBy('u.fullName', $direction);
|
||||
} elseif ($key === 'label') {
|
||||
$queryBuilder->addOrderBy('l.label', $direction);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$queryBuilder->orderBy('d.id', 'DESC');
|
||||
}
|
||||
|
||||
$docs = $queryBuilder->getQuery()->getArrayResult();
|
||||
|
||||
$dataTemp = [];
|
||||
foreach ($docs as $doc) {
|
||||
$item = [
|
||||
'id' => $doc['id'],
|
||||
'dateSubmit' => $doc['dateSubmit'],
|
||||
'date' => $doc['date'],
|
||||
'type' => $doc['type'],
|
||||
'code' => $doc['code'],
|
||||
'des' => $doc['des'],
|
||||
'amount' => $doc['amount'],
|
||||
'submitter' => $doc['submitter'],
|
||||
'label' => $doc['labelCode'] ? [
|
||||
'code' => $doc['labelCode'],
|
||||
'label' => $doc['labelLabel']
|
||||
] : null,
|
||||
'isPreview' => $doc['isPreview'],
|
||||
'isApproved' => $doc['isApproved'],
|
||||
'approvedBy' => $doc['approvedByName'] ? [
|
||||
'fullName' => $doc['approvedByName'],
|
||||
'id' => $doc['approvedById'],
|
||||
'email' => $doc['approvedByEmail']
|
||||
] : null,
|
||||
];
|
||||
|
||||
$mainRow = $entityManager->getRepository(HesabdariRow::class)
|
||||
->createQueryBuilder('r')
|
||||
->where('r.doc = :docId')
|
||||
->andWhere('r.person IS NOT NULL')
|
||||
->setParameter('docId', $doc['id'])
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
$item['person'] = $mainRow && $mainRow->getPerson() ? [
|
||||
'id' => $mainRow->getPerson()->getId(),
|
||||
'nikename' => $mainRow->getPerson()->getNikename(),
|
||||
'code' => $mainRow->getPerson()->getCode()
|
||||
] : null;
|
||||
|
||||
// استفاده از SQL خام برای محاسبه پرداختیها
|
||||
$sql = "
|
||||
SELECT SUM(rd.amount) as total_pays, COUNT(rd.id) as count_docs
|
||||
FROM hesabdari_doc rd
|
||||
JOIN hesabdari_doc_hesabdari_doc rel ON rel.hesabdari_doc_target = rd.id
|
||||
WHERE rel.hesabdari_doc_source = :sourceDocId
|
||||
AND rd.bid_id = :bidId
|
||||
";
|
||||
$stmt = $entityManager->getConnection()->prepare($sql);
|
||||
$stmt->bindValue('sourceDocId', $doc['id']);
|
||||
$stmt->bindValue('bidId', $acc['bid']->getId());
|
||||
$result = $stmt->executeQuery()->fetchAssociative();
|
||||
|
||||
$relatedDocsPays = $result['total_pays'] ?? 0;
|
||||
$relatedDocsCount = $result['count_docs'] ?? 0;
|
||||
|
||||
$item['relatedDocsCount'] = (int) $relatedDocsCount;
|
||||
$item['relatedDocsPays'] = $relatedDocsPays;
|
||||
$item['profit'] = $this->calculateProfit($doc['id'], $acc, $entityManager);
|
||||
$item['discountAll'] = 0;
|
||||
$item['transferCost'] = 0;
|
||||
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $doc]);
|
||||
foreach ($rows as $row) {
|
||||
if ($row->getRef()->getCode() == '104') {
|
||||
$item['discountAll'] = $row->getBd();
|
||||
} elseif ($row->getRef()->getCode() == '61') {
|
||||
$item['transferCost'] = $row->getBs();
|
||||
}
|
||||
}
|
||||
|
||||
$dataTemp[] = $item;
|
||||
}
|
||||
|
||||
// مرتبسازی توی PHP برای profit و receivedAmount
|
||||
if (!empty($sortBy)) {
|
||||
foreach ($sortBy as $sort) {
|
||||
$key = $sort['key'] ?? 'id';
|
||||
$direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? SORT_DESC : SORT_ASC;
|
||||
if ($key === 'profit') {
|
||||
usort($dataTemp, function ($a, $b) use ($direction) {
|
||||
return $direction === SORT_ASC ? $a['profit'] - $b['profit'] : $b['profit'] - $a['profit'];
|
||||
});
|
||||
} elseif ($key === 'receivedAmount') {
|
||||
usort($dataTemp, function ($a, $b) use ($direction) {
|
||||
return $direction === SORT_ASC ? $a['relatedDocsPays'] - $b['relatedDocsPays'] : $b['relatedDocsPays'] - $a['relatedDocsPays'];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// آمادهسازی دادهها برای Excel
|
||||
$excelData = [];
|
||||
$headers = [
|
||||
'ردیف',
|
||||
'کد فاکتور',
|
||||
'تاریخ',
|
||||
'خریدار',
|
||||
'وضعیت تایید',
|
||||
'تاییدکننده',
|
||||
'تخفیف',
|
||||
'حمل و نقل',
|
||||
'مبلغ',
|
||||
'سود فاکتور',
|
||||
'پرداختی',
|
||||
'برچسب',
|
||||
'شرح'
|
||||
];
|
||||
$excelData[] = $headers;
|
||||
|
||||
foreach ($dataTemp as $index => $item) {
|
||||
$excelData[] = [
|
||||
$index + 1,
|
||||
$item['code'],
|
||||
$item['date'],
|
||||
$item['person'] ? $item['person']['nikename'] : '-',
|
||||
$item['isApproved'] ? 'تایید شده' : ($item['isPreview'] ? 'در انتظار تایید' : 'تایید شده'),
|
||||
$item['approvedBy'] ? $item['approvedBy']['fullName'] : '-',
|
||||
number_format($item['discountAll']),
|
||||
number_format($item['transferCost']),
|
||||
number_format($item['amount']),
|
||||
number_format($item['profit']),
|
||||
number_format($item['relatedDocsPays']),
|
||||
$item['label'] ? $item['label']['label'] : '-',
|
||||
$item['des']
|
||||
];
|
||||
}
|
||||
|
||||
return new BinaryFileResponse($provider->createExcellFromArray($excelData));
|
||||
}
|
||||
|
||||
#[Route('/api/sell/list/pdf', name: 'app_sell_list_pdf', methods: ['POST'])]
|
||||
public function app_sell_list_pdf(
|
||||
Provider $provider,
|
||||
Request $request,
|
||||
Access $access,
|
||||
Log $log,
|
||||
EntityManagerInterface $entityManager,
|
||||
Jdate $jdate
|
||||
): JsonResponse {
|
||||
$acc = $access->hasRole('sell');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$searchTerm = $params['search'] ?? '';
|
||||
$types = $params['types'] ?? [];
|
||||
$dateFilter = $params['dateFilter'] ?? 'all';
|
||||
$sortBy = $params['sortBy'] ?? [];
|
||||
|
||||
$queryBuilder = $entityManager->createQueryBuilder()
|
||||
->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount')
|
||||
->addSelect('d.isPreview, d.isApproved')
|
||||
->addSelect('u.fullName as submitter')
|
||||
->addSelect('approver.fullName as approvedByName, approver.id as approvedById, approver.email as approvedByEmail')
|
||||
->addSelect('l.code as labelCode, l.label as labelLabel')
|
||||
->from(HesabdariDoc::class, 'd')
|
||||
->leftJoin('d.submitter', 'u')
|
||||
->leftJoin('d.approvedBy', 'approver')
|
||||
->leftJoin('d.InvoiceLabel', 'l')
|
||||
->leftJoin('d.hesabdariRows', 'r')
|
||||
->where('d.bid = :bid')
|
||||
->andWhere('d.year = :year')
|
||||
->andWhere('d.type = :type')
|
||||
->andWhere('d.money = :money')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('year', $acc['year'])
|
||||
->setParameter('type', 'sell')
|
||||
->setParameter('money', $acc['money']);
|
||||
|
||||
// اعمال فیلترهای تاریخ
|
||||
$today = $jdate->jdate('Y/m/d', time());
|
||||
if ($dateFilter === 'today') {
|
||||
$queryBuilder->andWhere('d.date = :today')
|
||||
->setParameter('today', $today);
|
||||
} elseif ($dateFilter === 'week') {
|
||||
$weekStart = $jdate->jdate('Y/m/d', strtotime('-6 days'));
|
||||
$queryBuilder->andWhere('d.date BETWEEN :weekStart AND :today')
|
||||
->setParameter('weekStart', $weekStart)
|
||||
->setParameter('today', $today);
|
||||
} elseif ($dateFilter === 'month') {
|
||||
$monthStart = $jdate->jdate('Y/m/01', time());
|
||||
$queryBuilder->andWhere('d.date BETWEEN :monthStart AND :today')
|
||||
->setParameter('monthStart', $monthStart)
|
||||
->setParameter('today', $today);
|
||||
}
|
||||
|
||||
if ($searchTerm) {
|
||||
$queryBuilder->leftJoin('r.person', 'p')
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->orX(
|
||||
'd.code LIKE :search',
|
||||
'd.des LIKE :search',
|
||||
'd.date LIKE :search',
|
||||
'd.amount LIKE :search',
|
||||
'p.nikename LIKE :search',
|
||||
'p.mobile LIKE :search'
|
||||
)
|
||||
)
|
||||
->setParameter('search', "%$searchTerm%");
|
||||
}
|
||||
|
||||
if (!empty($types)) {
|
||||
$queryBuilder->andWhere('l.code IN (:types)')
|
||||
->setParameter('types', $types);
|
||||
}
|
||||
|
||||
// اگر آیتمهای خاصی درخواست شدهاند
|
||||
if (array_key_exists('items', $params)) {
|
||||
$codes = array_map(function($item) { return $item['code']; }, $params['items']);
|
||||
$queryBuilder->andWhere('d.code IN (:codes)')
|
||||
->setParameter('codes', $codes);
|
||||
}
|
||||
|
||||
// اعمال مرتبسازی
|
||||
if (!empty($sortBy)) {
|
||||
foreach ($sortBy as $sort) {
|
||||
$key = $sort['key'] ?? 'id';
|
||||
$direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? 'DESC' : 'ASC';
|
||||
if ($key === 'profit' || $key === 'receivedAmount') {
|
||||
continue; // اینها توی PHP مرتب میشن
|
||||
} elseif (in_array($key, ['id', 'dateSubmit', 'date', 'type', 'code', 'des', 'amount', 'isPreview', 'isApproved'])) {
|
||||
$queryBuilder->addOrderBy('d.' . $key, $direction);
|
||||
} elseif ($key === 'submitter') {
|
||||
$queryBuilder->addOrderBy('u.fullName', $direction);
|
||||
} elseif ($key === 'label') {
|
||||
$queryBuilder->addOrderBy('l.label', $direction);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$queryBuilder->orderBy('d.id', 'DESC');
|
||||
}
|
||||
|
||||
$docs = $queryBuilder->getQuery()->getArrayResult();
|
||||
|
||||
$dataTemp = [];
|
||||
foreach ($docs as $doc) {
|
||||
$item = [
|
||||
'id' => $doc['id'],
|
||||
'dateSubmit' => $doc['dateSubmit'],
|
||||
'date' => $doc['date'],
|
||||
'type' => $doc['type'],
|
||||
'code' => $doc['code'],
|
||||
'des' => $doc['des'],
|
||||
'amount' => $doc['amount'],
|
||||
'submitter' => $doc['submitter'],
|
||||
'label' => $doc['labelCode'] ? [
|
||||
'code' => $doc['labelCode'],
|
||||
'label' => $doc['labelLabel']
|
||||
] : null,
|
||||
'isPreview' => $doc['isPreview'],
|
||||
'isApproved' => $doc['isApproved'],
|
||||
'approvedBy' => $doc['approvedByName'] ? [
|
||||
'fullName' => $doc['approvedByName'],
|
||||
'id' => $doc['approvedById'],
|
||||
'email' => $doc['approvedByEmail']
|
||||
] : null,
|
||||
];
|
||||
|
||||
$mainRow = $entityManager->getRepository(HesabdariRow::class)
|
||||
->createQueryBuilder('r')
|
||||
->where('r.doc = :docId')
|
||||
->andWhere('r.person IS NOT NULL')
|
||||
->setParameter('docId', $doc['id'])
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
$item['person'] = $mainRow && $mainRow->getPerson() ? [
|
||||
'id' => $mainRow->getPerson()->getId(),
|
||||
'nikename' => $mainRow->getPerson()->getNikename(),
|
||||
'code' => $mainRow->getPerson()->getCode()
|
||||
] : null;
|
||||
|
||||
// استفاده از SQL خام برای محاسبه پرداختیها
|
||||
$sql = "
|
||||
SELECT SUM(rd.amount) as total_pays, COUNT(rd.id) as count_docs
|
||||
FROM hesabdari_doc rd
|
||||
JOIN hesabdari_doc_hesabdari_doc rel ON rel.hesabdari_doc_target = rd.id
|
||||
WHERE rel.hesabdari_doc_source = :sourceDocId
|
||||
AND rd.bid_id = :bidId
|
||||
";
|
||||
$stmt = $entityManager->getConnection()->prepare($sql);
|
||||
$stmt->bindValue('sourceDocId', $doc['id']);
|
||||
$stmt->bindValue('bidId', $acc['bid']->getId());
|
||||
$result = $stmt->executeQuery()->fetchAssociative();
|
||||
|
||||
$relatedDocsPays = $result['total_pays'] ?? 0;
|
||||
$relatedDocsCount = $result['count_docs'] ?? 0;
|
||||
|
||||
$item['relatedDocsCount'] = (int) $relatedDocsCount;
|
||||
$item['relatedDocsPays'] = $relatedDocsPays;
|
||||
$item['profit'] = $this->calculateProfit($doc['id'], $acc, $entityManager);
|
||||
$item['discountAll'] = 0;
|
||||
$item['transferCost'] = 0;
|
||||
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $doc]);
|
||||
foreach ($rows as $row) {
|
||||
if ($row->getRef()->getCode() == '104') {
|
||||
$item['discountAll'] = $row->getBd();
|
||||
} elseif ($row->getRef()->getCode() == '61') {
|
||||
$item['transferCost'] = $row->getBs();
|
||||
}
|
||||
}
|
||||
|
||||
$dataTemp[] = $item;
|
||||
}
|
||||
|
||||
// مرتبسازی توی PHP برای profit و receivedAmount
|
||||
if (!empty($sortBy)) {
|
||||
foreach ($sortBy as $sort) {
|
||||
$key = $sort['key'] ?? 'id';
|
||||
$direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? SORT_DESC : SORT_ASC;
|
||||
if ($key === 'profit') {
|
||||
usort($dataTemp, function ($a, $b) use ($direction) {
|
||||
return $direction === SORT_ASC ? $a['profit'] - $b['profit'] : $b['profit'] - $a['profit'];
|
||||
});
|
||||
} elseif ($key === 'receivedAmount') {
|
||||
usort($dataTemp, function ($a, $b) use ($direction) {
|
||||
return $direction === SORT_ASC ? $a['relatedDocsPays'] - $b['relatedDocsPays'] : $b['relatedDocsPays'] - $a['relatedDocsPays'];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html = $this->renderView('pdf/sell_list.html.twig', [
|
||||
'items' => $dataTemp,
|
||||
'bid' => $acc['bid'],
|
||||
'currentPage' => 1,
|
||||
'totalPages' => 1,
|
||||
'totalItems' => count($dataTemp),
|
||||
'page_title' => 'لیست فاکتورهای فروش'
|
||||
]);
|
||||
|
||||
$pdfPid = $provider->createPrint(
|
||||
$acc['bid'],
|
||||
$this->getUser(),
|
||||
$html,
|
||||
false,
|
||||
'A4-L'
|
||||
);
|
||||
|
||||
return $this->json(['id' => $pdfPid]);
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@
|
|||
<td class="center item">توضیحات</td>
|
||||
<td class="center item">شرح سند</td>
|
||||
<td class="center item">تفصیل</td>
|
||||
<td class="center item">طرف حسابها</td>
|
||||
<td class="center item">بدهکار</td>
|
||||
<td class="center item">بستانکار</td>
|
||||
<td class="center item">سال مالی</td>
|
||||
|
|
@ -66,6 +67,7 @@
|
|||
<td class="center item">{{ item.des }}</td>
|
||||
<td class="center item">{{ item.doc.des }}</td>
|
||||
<td class="center item">{{ item.ref.name }}</td>
|
||||
<td class="center item">{{ item.counterpartAccounts }}</td>
|
||||
<td class="center item">{{ item.bd | number_format }}</td>
|
||||
<td class="center item">{{ item.bs | number_format }}</td>
|
||||
<td class="center item">{{ item.year.label }}</td>
|
||||
|
|
|
|||
53
hesabixCore/templates/pdf/sell_list.html.twig
Normal file
53
hesabixCore/templates/pdf/sell_list.html.twig
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{% extends "pdf/base.html.twig" %}
|
||||
{% block body %}
|
||||
<div style="width:100%;margin-top:5px;text-align:center;">
|
||||
<table style="width:100%;">
|
||||
<tbody>
|
||||
<tr class="stimol" style="text-align: center; background-color: grey; text-color: white">
|
||||
<td style="width: 35px;">ردیف</td>
|
||||
<td class="center item">کد فاکتور</td>
|
||||
<td class="center item">تاریخ</td>
|
||||
<td class="center item">خریدار</td>
|
||||
<td class="center item">وضعیت تایید</td>
|
||||
<td class="center item">تاییدکننده</td>
|
||||
<td class="center item">تخفیف</td>
|
||||
<td class="center item">حمل و نقل</td>
|
||||
<td class="center item">مبلغ</td>
|
||||
<td class="center item">سود فاکتور</td>
|
||||
<td class="center item">پرداختی</td>
|
||||
<td class="center item">برچسب</td>
|
||||
<td class="center item">شرح</td>
|
||||
</tr>
|
||||
{% for item in items %}
|
||||
<tr class="stimol {% if loop.index is even%}bg-dark text-light{% endif%}">
|
||||
<td class="center item">{{ loop.index + ((currentPage - 1) * 10) }}</td>
|
||||
<td class="center item">{{ item.code }}</td>
|
||||
<td class="center item">{{ item.date }}</td>
|
||||
<td class="center item">{{ item.person ? item.person.nikename : '-' }}</td>
|
||||
<td class="center item">
|
||||
{% if item.isApproved %}
|
||||
تایید شده
|
||||
{% elseif item.isPreview %}
|
||||
در انتظار تایید
|
||||
{% else %}
|
||||
تایید شده
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center item">{{ item.approvedBy ? item.approvedBy.fullName : '-' }}</td>
|
||||
<td class="center item">{{ item.discountAll | number_format }}</td>
|
||||
<td class="center item">{{ item.transferCost | number_format }}</td>
|
||||
<td class="center item">{{ item.amount | number_format }}</td>
|
||||
<td class="center item">{{ item.profit | number_format }}</td>
|
||||
<td class="center item">{{ item.relatedDocsPays | number_format }}</td>
|
||||
<td class="center item">{{ item.label ? item.label.label : '-' }}</td>
|
||||
<td class="center item">{{ item.des }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px; text-align: left;">
|
||||
<small>صفحه {{ currentPage }} از {{ totalPages }} - تعداد کل: {{ totalItems }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -191,6 +191,19 @@
|
|||
{{ item.settlement }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template v-slot:item.accounts="{ item }">
|
||||
<div v-for="account in item.accounts" :key="account.name" class="mb-1">
|
||||
<v-chip
|
||||
:color="getAccountColor(account.type)"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
class="me-1"
|
||||
>
|
||||
{{ account.name }}
|
||||
<span class="ms-1">({{ account.formattedAmount }})</span>
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.balance="{ item }">
|
||||
<span :class="{
|
||||
'text-success': -item.balance > 0,
|
||||
|
|
@ -234,6 +247,7 @@ export default {
|
|||
{ title: this.$t('dialog.invoice_num'), key: "code", align: "center", sortable: true },
|
||||
{ title: this.$t('dialog.date'), key: "date", align: "center", sortable: true },
|
||||
{ title: this.$t('app.body'), key: "des", align: "center" },
|
||||
{ title: 'طرف حسابها', key: "accounts", align: "center", sortable: false },
|
||||
{ title: this.$t('pages.bank_card.detail'), key: "ref", align: "center", sortable: true },
|
||||
{ title: this.$t('pages.bank_card.deposit'), key: "bd", align: "center", sortable: true },
|
||||
{ title: this.$t('pages.bank_card.withdrawal'), key: "bs", align: "center", sortable: true },
|
||||
|
|
@ -293,10 +307,34 @@ export default {
|
|||
this.items.forEach((item) => {
|
||||
item.bs = this.$filters.formatNumber(item.bs)
|
||||
item.bd = this.$filters.formatNumber(item.bd)
|
||||
item.accounts = []; // Initialize accounts array
|
||||
})
|
||||
this.loadCounterpartAccounts(id);
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
loadCounterpartAccounts(bankCode) {
|
||||
axios.post('/api/person/bank/accounts/list', {
|
||||
bankCode: bankCode
|
||||
}).then((response) => {
|
||||
// Group accounts by document code
|
||||
const accountsByDoc = {};
|
||||
response.data.forEach(doc => {
|
||||
accountsByDoc[doc.docId] = doc.accounts;
|
||||
});
|
||||
|
||||
// Add accounts to items
|
||||
this.items.forEach(item => {
|
||||
item.accounts = accountsByDoc[item.code] || [];
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('Error loading counterpart accounts:', error);
|
||||
// Set empty accounts array for all items
|
||||
this.items.forEach(item => {
|
||||
item.accounts = [];
|
||||
});
|
||||
});
|
||||
},
|
||||
getTypeRoute(type, code) {
|
||||
const routes = {
|
||||
sell: '/acc/sell/view/',
|
||||
|
|
@ -347,6 +385,20 @@ export default {
|
|||
};
|
||||
return labels[type] || type;
|
||||
},
|
||||
getAccountColor(type) {
|
||||
switch (type) {
|
||||
case 'bank':
|
||||
return 'primary';
|
||||
case 'cashdesk':
|
||||
return 'success';
|
||||
case 'salary':
|
||||
return 'warning';
|
||||
case 'person':
|
||||
return 'info';
|
||||
default:
|
||||
return 'grey';
|
||||
}
|
||||
},
|
||||
excellOutput(AllItems = true) {
|
||||
if (AllItems) {
|
||||
this.loading = true;
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@
|
|||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<small class="mb-2">مبلغ</small>
|
||||
<money3 @change="calc()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
<money3 @change="calcFromAccount()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
</money3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -203,7 +203,7 @@
|
|||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<small class="mb-2">مبلغ</small>
|
||||
<money3 @change="calc()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
<money3 @change="calcFromAccount()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
</money3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -252,7 +252,7 @@
|
|||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<small class="mb-2">مبلغ</small>
|
||||
<money3 @change="calc()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
<money3 @change="calcFromAccount()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
</money3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -417,9 +417,69 @@ export default {
|
|||
side = parseInt(side) + parseInt(item.amount);
|
||||
});
|
||||
this.balance = parseInt(this.sum) - parseInt(side);
|
||||
|
||||
// Auto-sync amounts when there's only one person and one account side
|
||||
this.autoSyncAmounts();
|
||||
|
||||
this.funcCanSubmit();
|
||||
|
||||
},
|
||||
autoSyncAmounts() {
|
||||
// Check if there's exactly one person and one account side
|
||||
const totalPersons = this.persons.length;
|
||||
const totalBanks = this.banks.length;
|
||||
const totalCashdesks = this.cashdesks.length;
|
||||
const totalSalarys = this.salarys.length;
|
||||
|
||||
const totalAccountSides = totalBanks + totalCashdesks + totalSalarys;
|
||||
|
||||
// Only auto-sync if there's exactly one person and one account side
|
||||
if (totalPersons === 1 && totalAccountSides === 1) {
|
||||
const personAmount = parseInt(this.persons[0].amount) || 0;
|
||||
|
||||
// Sync to the single account side
|
||||
if (totalBanks === 1) {
|
||||
this.banks[0].amount = personAmount;
|
||||
} else if (totalCashdesks === 1) {
|
||||
this.cashdesks[0].amount = personAmount;
|
||||
} else if (totalSalarys === 1) {
|
||||
this.salarys[0].amount = personAmount;
|
||||
}
|
||||
}
|
||||
},
|
||||
autoSyncFromAccount() {
|
||||
// Check if there's exactly one person and one account side
|
||||
const totalPersons = this.persons.length;
|
||||
const totalBanks = this.banks.length;
|
||||
const totalCashdesks = this.cashdesks.length;
|
||||
const totalSalarys = this.salarys.length;
|
||||
|
||||
const totalAccountSides = totalBanks + totalCashdesks + totalSalarys;
|
||||
|
||||
// Only auto-sync if there's exactly one person and one account side
|
||||
if (totalPersons === 1 && totalAccountSides === 1) {
|
||||
let accountAmount = 0;
|
||||
|
||||
// Get amount from the single account side
|
||||
if (totalBanks === 1) {
|
||||
accountAmount = parseInt(this.banks[0].amount) || 0;
|
||||
} else if (totalCashdesks === 1) {
|
||||
accountAmount = parseInt(this.cashdesks[0].amount) || 0;
|
||||
} else if (totalSalarys === 1) {
|
||||
accountAmount = parseInt(this.salarys[0].amount) || 0;
|
||||
}
|
||||
|
||||
// Sync to the person
|
||||
this.persons[0].amount = accountAmount;
|
||||
}
|
||||
},
|
||||
calcFromAccount() {
|
||||
// First sync from account to person
|
||||
this.autoSyncFromAccount();
|
||||
|
||||
// Then run normal calculation
|
||||
this.calc();
|
||||
},
|
||||
funcCanSubmit() {
|
||||
//check form can submit
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@
|
|||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<small class="mb-2">مبلغ</small>
|
||||
<money3 @change="calc()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
<money3 @change="calcFromAccount()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
</money3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -202,7 +202,7 @@
|
|||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<small class="mb-2">مبلغ</small>
|
||||
<money3 @change="calc()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
<money3 @change="calcFromAccount()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
</money3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -251,7 +251,7 @@
|
|||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<small class="mb-2">مبلغ</small>
|
||||
<money3 @change="calc()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
<money3 @change="calcFromAccount()" class="form-control" v-model="item.amount" v-bind="currencyConfig">
|
||||
</money3>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -416,9 +416,69 @@ export default {
|
|||
side = parseInt(side) + parseInt(item.amount);
|
||||
});
|
||||
this.balance = parseInt(this.sum) - parseInt(side);
|
||||
|
||||
// Auto-sync amounts when there's only one person and one account side
|
||||
this.autoSyncAmounts();
|
||||
|
||||
this.funcCanSubmit();
|
||||
|
||||
},
|
||||
autoSyncAmounts() {
|
||||
// Check if there's exactly one person and one account side
|
||||
const totalPersons = this.persons.length;
|
||||
const totalBanks = this.banks.length;
|
||||
const totalCashdesks = this.cashdesks.length;
|
||||
const totalSalarys = this.salarys.length;
|
||||
|
||||
const totalAccountSides = totalBanks + totalCashdesks + totalSalarys;
|
||||
|
||||
// Only auto-sync if there's exactly one person and one account side
|
||||
if (totalPersons === 1 && totalAccountSides === 1) {
|
||||
const personAmount = parseInt(this.persons[0].amount) || 0;
|
||||
|
||||
// Sync to the single account side
|
||||
if (totalBanks === 1) {
|
||||
this.banks[0].amount = personAmount;
|
||||
} else if (totalCashdesks === 1) {
|
||||
this.cashdesks[0].amount = personAmount;
|
||||
} else if (totalSalarys === 1) {
|
||||
this.salarys[0].amount = personAmount;
|
||||
}
|
||||
}
|
||||
},
|
||||
autoSyncFromAccount() {
|
||||
// Check if there's exactly one person and one account side
|
||||
const totalPersons = this.persons.length;
|
||||
const totalBanks = this.banks.length;
|
||||
const totalCashdesks = this.cashdesks.length;
|
||||
const totalSalarys = this.salarys.length;
|
||||
|
||||
const totalAccountSides = totalBanks + totalCashdesks + totalSalarys;
|
||||
|
||||
// Only auto-sync if there's exactly one person and one account side
|
||||
if (totalPersons === 1 && totalAccountSides === 1) {
|
||||
let accountAmount = 0;
|
||||
|
||||
// Get amount from the single account side
|
||||
if (totalBanks === 1) {
|
||||
accountAmount = parseInt(this.banks[0].amount) || 0;
|
||||
} else if (totalCashdesks === 1) {
|
||||
accountAmount = parseInt(this.cashdesks[0].amount) || 0;
|
||||
} else if (totalSalarys === 1) {
|
||||
accountAmount = parseInt(this.salarys[0].amount) || 0;
|
||||
}
|
||||
|
||||
// Sync to the person
|
||||
this.persons[0].amount = accountAmount;
|
||||
}
|
||||
},
|
||||
calcFromAccount() {
|
||||
// First sync from account to person
|
||||
this.autoSyncFromAccount();
|
||||
|
||||
// Then run normal calculation
|
||||
this.calc();
|
||||
},
|
||||
funcCanSubmit() {
|
||||
//check form can submit
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ export default defineComponent({
|
|||
canPdf: true,
|
||||
canPrint: true,
|
||||
canPrintCashdeskRecp: false,
|
||||
canPos: false,
|
||||
update: 0,
|
||||
printOptions: {},
|
||||
commodity: [],
|
||||
selectedCommodity: null,
|
||||
tempID: '',
|
||||
|
|
@ -70,7 +72,8 @@ export default defineComponent({
|
|||
units: [],
|
||||
persons: [],
|
||||
person: {
|
||||
nikename: ''
|
||||
nikename: '',
|
||||
id: null
|
||||
},
|
||||
cashdesks: [],
|
||||
cashdesk: null,
|
||||
|
|
@ -204,9 +207,7 @@ export default defineComponent({
|
|||
});
|
||||
axios.post("/api/printers/options/info").then((response) => {
|
||||
this.loading = false;
|
||||
this.canPdf = response.data.fastsell.pdf;
|
||||
this.canPrintCashdeskRecp = response.data.fastsell.cashdeskTicket;
|
||||
this.canPrint = response.data.fastsell.invoice;
|
||||
this.printOptions = response.data.sell;
|
||||
});
|
||||
},
|
||||
save() {
|
||||
|
|
@ -240,7 +241,7 @@ export default defineComponent({
|
|||
});
|
||||
if (canAdd) {
|
||||
this.loading = true;
|
||||
let outItems = [
|
||||
let outItems: any[] = [
|
||||
...this.data.items
|
||||
];
|
||||
//save data
|
||||
|
|
@ -261,7 +262,8 @@ export default defineComponent({
|
|||
date: this.data.date,
|
||||
des: this.data.des,
|
||||
rows: outItems,
|
||||
update: ''
|
||||
update: '',
|
||||
autoApprove: true
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
if (response.data.result == '1') {
|
||||
|
|
@ -270,9 +272,10 @@ export default defineComponent({
|
|||
axios.post('/api/sell/print/invoice', {
|
||||
code: this.update,
|
||||
pdf: this.canPdf,
|
||||
posPrint: this.canPrint,
|
||||
printers: this.canPrint,
|
||||
posPrint: this.canPos,
|
||||
posPrintRecp: this.canPrintCashdeskRecp,
|
||||
printers: this.canPdf
|
||||
printOptions: this.printOptions
|
||||
}).then((response) => {
|
||||
if (this.canPdf) {
|
||||
this.printID = response.data.id;
|
||||
|
|
@ -305,6 +308,7 @@ export default defineComponent({
|
|||
des: 'دریافت وجه فاکتور',
|
||||
rows: outItems,
|
||||
update: '',
|
||||
autoApprove: true,
|
||||
related: response.data.doc.code
|
||||
}).then((response) => {
|
||||
if (response.data.result == '4') {
|
||||
|
|
@ -625,6 +629,15 @@ export default defineComponent({
|
|||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-4">
|
||||
<span class="form-check form-switch form-check-inline">
|
||||
<input :disabled="this.loading" v-model="canPos" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">
|
||||
<i class="fa-solid fa-receipt me-1"></i>
|
||||
صورت حساب POS
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-4">
|
||||
<span class="form-check form-switch form-check-inline">
|
||||
<input :disabled="this.loading" v-model="canPdf" class="form-check-input" type="checkbox">
|
||||
|
|
|
|||
|
|
@ -20,6 +20,16 @@
|
|||
<v-btn v-bind="props" icon="mdi-delete" color="danger" @click="deleteItems()"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip :text="$t('dialog.export_excel')" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-file-excel" color="green" @click="exportToExcel()" :loading="excelLoading"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip :text="$t('dialog.export_pdf')" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-file-pdf-box" color="red" @click="exportToPdf()" :loading="pdfLoading"></v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip v-if="isPluginActive('taxsettings')" text="ارسال گروهی به کارپوشه مودیان" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-cloud-upload" color="orange" @click="sendBulkToTaxSystem()"
|
||||
|
|
@ -414,6 +424,8 @@ export default defineComponent({
|
|||
types: [],
|
||||
loading: false,
|
||||
bulkLoading: false,
|
||||
excelLoading: false,
|
||||
pdfLoading: false,
|
||||
items: [],
|
||||
itemsApproved: [],
|
||||
itemsPending: [],
|
||||
|
|
@ -1069,6 +1081,102 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
},
|
||||
async exportToExcel() {
|
||||
this.excelLoading = true;
|
||||
try {
|
||||
const params = {
|
||||
search: this.searchValue,
|
||||
types: this.types.filter(t => t.checked).map(t => t.code),
|
||||
dateFilter: this.dateFilter,
|
||||
sortBy: this.serverOptions.sortBy,
|
||||
};
|
||||
|
||||
// اگر آیتمهای خاصی انتخاب شدهاند، فقط آنها را export کن
|
||||
if (this.itemsSelected.length > 0) {
|
||||
params.items = this.itemsSelected.map(code => ({ code }));
|
||||
}
|
||||
|
||||
const response = await axios.post('/api/sell/list/excel', params, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
const fileURL = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const fileLink = document.createElement('a');
|
||||
fileLink.href = fileURL;
|
||||
fileLink.setAttribute('download', `لیست_فاکتورهای_فروش_${new Date().toLocaleDateString('fa-IR')}.xlsx`);
|
||||
document.body.appendChild(fileLink);
|
||||
fileLink.click();
|
||||
document.body.removeChild(fileLink);
|
||||
window.URL.revokeObjectURL(fileURL);
|
||||
|
||||
Swal.fire({
|
||||
text: 'فایل اکسل با موفقیت دانلود شد',
|
||||
icon: 'success',
|
||||
confirmButtonText: 'قبول'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error exporting to Excel:', error);
|
||||
Swal.fire({
|
||||
text: 'خطا در ایجاد فایل اکسل: ' + (error.response?.data?.message || error.message),
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
});
|
||||
} finally {
|
||||
this.excelLoading = false;
|
||||
}
|
||||
},
|
||||
async exportToPdf() {
|
||||
this.pdfLoading = true;
|
||||
try {
|
||||
const params = {
|
||||
search: this.searchValue,
|
||||
types: this.types.filter(t => t.checked).map(t => t.code),
|
||||
dateFilter: this.dateFilter,
|
||||
sortBy: this.serverOptions.sortBy,
|
||||
};
|
||||
|
||||
// اگر آیتمهای خاصی انتخاب شدهاند، فقط آنها را export کن
|
||||
if (this.itemsSelected.length > 0) {
|
||||
params.items = this.itemsSelected.map(code => ({ code }));
|
||||
}
|
||||
|
||||
const response = await axios.post('/api/sell/list/pdf', params);
|
||||
|
||||
if (response.data && response.data.id) {
|
||||
const pdfResponse = await axios({
|
||||
method: 'get',
|
||||
url: '/front/print/' + response.data.id,
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
|
||||
const fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
|
||||
const fileLink = document.createElement('a');
|
||||
fileLink.href = fileURL;
|
||||
fileLink.setAttribute('download', `لیست_فاکتورهای_فروش_${new Date().toLocaleDateString('fa-IR')}.pdf`);
|
||||
document.body.appendChild(fileLink);
|
||||
fileLink.click();
|
||||
document.body.removeChild(fileLink);
|
||||
window.URL.revokeObjectURL(fileURL);
|
||||
|
||||
Swal.fire({
|
||||
text: 'فایل PDF با موفقیت دانلود شد',
|
||||
icon: 'success',
|
||||
confirmButtonText: 'قبول'
|
||||
});
|
||||
} else {
|
||||
throw new Error('خطا در دریافت شناسه چاپ');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error exporting to PDF:', error);
|
||||
Swal.fire({
|
||||
text: 'خطا در ایجاد فایل PDF: ' + (error.response?.data?.message || error.message),
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول'
|
||||
});
|
||||
} finally {
|
||||
this.pdfLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadColumnSettings();
|
||||
|
|
|
|||
Loading…
Reference in a new issue