Compare commits

...

2 commits

5 changed files with 468 additions and 0 deletions

View file

@ -19,6 +19,7 @@ use App\Entity\StoreroomTicket;
use App\Service\Printers;
use App\Service\registryMGR;
use App\Service\SMS;
use App\Service\PreInvoiceConversionService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@ -385,4 +386,109 @@ class PreinvoiceController extends AbstractController
return $this->json(['id' => $pdfPid]);
}
#[Route('/api/preinvoice/convert-to-invoice/{code}', name: 'app_preinvoice_convert_to_invoice', methods: ['POST'])]
public function convertToInvoice(
string $code,
Request $request,
Access $access,
PreInvoiceConversionService $conversionService,
EntityManagerInterface $entityManager
): JsonResponse {
$acc = $access->hasRole('plugAccproPresell');
if (!$acc) {
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
}
// پیدا کردن پیش‌فاکتور
$preinvoice = $entityManager->getRepository(PreInvoiceDoc::class)->findOneBy([
'bid' => $acc['bid'],
'code' => $code,
'year' => $acc['year']
]);
if (!$preinvoice) {
return new JsonResponse($this->extractor->operationFail('پیش‌فاکتور یافت نشد'), 404);
}
// بررسی امکان تبدیل
$canConvert = $conversionService->canConvert($preinvoice);
if (!$canConvert['canConvert']) {
return new JsonResponse($this->extractor->operationFail(implode(', ', $canConvert['errors'])), 400);
}
// انجام تبدیل
$result = $conversionService->convertToInvoice($preinvoice, $this->getUser(), $acc);
if ($result['success']) {
return new JsonResponse($result);
} else {
return new JsonResponse($this->extractor->operationFail($result['message']), 500);
}
}
#[Route('/api/preinvoice/can-convert/{code}', name: 'app_preinvoice_can_convert', methods: ['GET'])]
public function canConvertToInvoice(
string $code,
Access $access,
PreInvoiceConversionService $conversionService,
EntityManagerInterface $entityManager
): JsonResponse {
$acc = $access->hasRole('plugAccproPresell');
if (!$acc) {
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
}
// پیدا کردن پیش‌فاکتور
$preinvoice = $entityManager->getRepository(PreInvoiceDoc::class)->findOneBy([
'bid' => $acc['bid'],
'code' => $code,
'year' => $acc['year']
]);
if (!$preinvoice) {
return new JsonResponse($this->extractor->operationFail('پیش‌فاکتور یافت نشد'), 404);
}
// بررسی امکان تبدیل
$result = $conversionService->canConvert($preinvoice);
return new JsonResponse([
'canConvert' => $result['canConvert'],
'errors' => $result['errors']
]);
}
#[Route('/api/preinvoice/test-values/{code}', name: 'app_preinvoice_test_values', methods: ['GET'])]
public function testPreInvoiceValues(
string $code,
Access $access,
EntityManagerInterface $entityManager
): JsonResponse {
$acc = $access->hasRole('plugAccproPresell');
if (!$acc) {
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
}
// پیدا کردن پیش‌فاکتور
$preinvoice = $entityManager->getRepository(PreInvoiceDoc::class)->findOneBy([
'bid' => $acc['bid'],
'code' => $code,
'year' => $acc['year']
]);
if (!$preinvoice) {
return new JsonResponse($this->extractor->operationFail('پیش‌فاکتور یافت نشد'), 404);
}
return new JsonResponse([
'totalDiscount' => $preinvoice->getTotalDiscount(),
'totalDiscountPercent' => $preinvoice->getTotalDiscountPercent(),
'shippingCost' => $preinvoice->getShippingCost(),
'showTotalPercentDiscount' => $preinvoice->isShowTotalPercentDiscount(),
'amount' => $preinvoice->getAmount(),
'totalDiscountFloat' => floatval($preinvoice->getTotalDiscount() ?: 0),
'shippingCostFloat' => floatval($preinvoice->getShippingCost() ?: 0)
]);
}
}

View file

@ -36,4 +36,24 @@ class Log
$this->em->persist($log);
$this->em->flush();
}
/**
* ثبت لاگ برای عملیات پیش‌فاکتور
*/
public function insertPreInvoiceLog(string $part, string $des, User | null $user = null, Business | string | null $bid = null): void
{
if(is_string($bid))
$bid = $this->em->getRepository(Business::class)->find($bid);
$log = new \App\Entity\Log();
$log->setDateSubmit(time());
$log->setPart($part);
$log->setDes($des);
$log->setUser($user);
$log->setBid($bid);
$log->setDoc(null); // برای پیش‌فاکتور، doc را null قرار می‌دهیم
$log->setRepserviceOrder(null);
$log->setIpaddress($this->remoteAddress->getIpAddress());
$this->em->persist($log);
$this->em->flush();
}
}

View file

@ -0,0 +1,285 @@
<?php
namespace App\Service;
use App\Entity\PreInvoiceDoc;
use App\Entity\HesabdariDoc;
use App\Entity\HesabdariRow;
use App\Entity\HesabdariTable;
use App\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class PreInvoiceConversionService
{
private EntityManagerInterface $entityManager;
private Provider $provider;
private Log $log;
public function __construct(
EntityManagerInterface $entityManager,
Provider $provider,
Log $log
) {
$this->entityManager = $entityManager;
$this->provider = $provider;
$this->log = $log;
}
/**
* تبدیل پیش‌فاکتور به فاکتور فروش
*/
public function convertToInvoice(PreInvoiceDoc $preInvoice, UserInterface $user, array $acc): array
{
try {
// بررسی وضعیت پیش‌فاکتور
if ($preInvoice->getStatus() === 'converted') {
throw new \Exception('این پیش‌فاکتور قبلاً به فاکتور فروش تبدیل شده است');
}
// ایجاد فاکتور فروش جدید
$sellDoc = new HesabdariDoc();
$sellDoc->setBid($acc['bid']);
$sellDoc->setYear($acc['year']);
$sellDoc->setDateSubmit(time());
$sellDoc->setType('sell');
$sellDoc->setSubmitter($user);
$sellDoc->setMoney($acc['money']);
$sellDoc->setCode($this->provider->getAccountingCode($acc['bid'], 'accounting'));
$sellDoc->setDate($preInvoice->getDate());
$sellDoc->setDes($preInvoice->getDes() ?: 'فاکتور فروش تبدیل شده از پیش‌فاکتور شماره ' . $preInvoice->getCode());
// مبلغ کل موقت - بعداً به‌روزرسانی می‌شود
$sellDoc->setAmount($preInvoice->getAmount());
$sellDoc->setTaxPercent($preInvoice->getTaxPercent() ?: 0);
$sellDoc->setStatus('approved');
// تنظیم تخفیف کل
if ($preInvoice->getTotalDiscount() || $preInvoice->getTotalDiscountPercent()) {
$sellDoc->setDiscountType($preInvoice->isShowTotalPercentDiscount() ? 'percent' : 'amount');
$sellDoc->setDiscountPercent(floatval($preInvoice->getTotalDiscountPercent() ?: 0));
}
// تنظیم برچسب فاکتور اگر وجود داشته باشد
if ($preInvoice->getInvoiceLabel()) {
$sellDoc->setInvoiceLabel($preInvoice->getInvoiceLabel());
}
// تنظیم فروشنده اگر وجود داشته باشد
if ($preInvoice->getSalesman()) {
$sellDoc->setSalesman($preInvoice->getSalesman());
}
$this->entityManager->persist($sellDoc);
// تبدیل آیتم‌های پیش‌فاکتور به ردیف‌های فاکتور فروش
$sumTotal = 0;
$sumTax = 0;
$totalItemDiscount = 0;
// تعریف متغیرهای تخفیف و هزینه حمل
$totalDiscountAmount = floatval($preInvoice->getTotalDiscount() ?: 0);
$shippingCost = floatval($preInvoice->getShippingCost() ?: 0);
foreach ($preInvoice->getPreInvoiceItems() as $preItem) {
$hesabdariRow = new HesabdariRow();
$hesabdariRow->setDes($preItem->getDes() ?: 'فروش کالا');
$hesabdariRow->setBid($acc['bid']);
$hesabdariRow->setYear($acc['year']);
$hesabdariRow->setDoc($sellDoc);
$itemTotal = $preItem->getCommodityCount() * $preItem->getBs();
$itemDiscount = $preItem->getDiscountAmount() ?: 0;
// محاسبه تخفیف درصدی اگر تخفیف مقداری صفر باشد
if ($itemDiscount == 0 && $preItem->getDiscountPercent() && $preItem->getDiscountPercent() > 0) {
$itemDiscount = ($itemTotal * floatval($preItem->getDiscountPercent())) / 100;
}
$itemTotalAfterDiscount = $itemTotal - $itemDiscount;
// ذخیره مبلغ کل کالا بعد از تخفیف در فیلد bs - مطابق با انتظار Controller
$hesabdariRow->setBs($itemTotalAfterDiscount);
$hesabdariRow->setBd(0);
$hesabdariRow->setCommdityCount($preItem->getCommodityCount());
$hesabdariRow->setCommodity($preItem->getCommodity());
$hesabdariRow->setDiscount($itemDiscount);
$hesabdariRow->setDiscountPercent($preItem->getDiscountPercent() ?: 0);
$hesabdariRow->setDiscountType($preItem->isShowPercentDiscount() ? 'percent' : 'amount');
// محاسبه مالیات
$taxAmount = 0;
if ($preInvoice->getTaxPercent() && $preInvoice->getTaxPercent() > 0) {
$taxAmount = ($itemTotalAfterDiscount * $preInvoice->getTaxPercent()) / 100;
$hesabdariRow->setTax($taxAmount);
$sumTax += $taxAmount;
}
// تنظیم مرجع حسابداری (جدول 1 برای فروش)
$ref = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '1']);
$hesabdariRow->setRef($ref);
$this->entityManager->persist($hesabdariRow);
$sumTotal += $itemTotalAfterDiscount;
$totalItemDiscount += $itemDiscount;
}
// محاسبه تخفیف درصدی اگر مبلغ تخفیف صفر باشد اما درصد تخفیف وجود داشته باشد
if ($totalDiscountAmount == 0 && $preInvoice->getTotalDiscountPercent() && $preInvoice->getTotalDiscountPercent() > 0) {
$totalDiscountAmount = ($sumTotal * floatval($preInvoice->getTotalDiscountPercent())) / 100;
}
// افزودن ردیف مالیات کل
if ($sumTax > 0) {
$taxRow = new HesabdariRow();
$taxRow->setDes('مالیات بر ارزش افزوده');
$taxRow->setBid($acc['bid']);
$taxRow->setYear($acc['year']);
$taxRow->setDoc($sellDoc);
$taxRow->setBs($sumTax);
$taxRow->setBd(0);
$taxRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '33']);
$taxRow->setRef($taxRef);
$this->entityManager->persist($taxRow);
}
// افزودن ردیف تخفیف کل
if ($totalDiscountAmount > 0) {
$discountRow = new HesabdariRow();
$discountRow->setDes('تخفیف کل');
$discountRow->setBid($acc['bid']);
$discountRow->setYear($acc['year']);
$discountRow->setDoc($sellDoc);
$discountRow->setBs(0);
$discountRow->setBd($totalDiscountAmount);
$discountRow->setDiscount($totalDiscountAmount);
$discountRow->setDiscountPercent(floatval($preInvoice->getTotalDiscountPercent() ?: 0));
$discountRow->setDiscountType($preInvoice->isShowTotalPercentDiscount() ? 'percent' : 'amount');
// استفاده از جدول تخفیف (کد 104) - مطابق با Controller فاکتور فروش
$discountRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '104']);
if (!$discountRef) {
// اگر جدول تخفیف وجود نداشت، از جدول 4 استفاده کن
$discountRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '4']);
if (!$discountRef) {
// اگر جدول 4 هم وجود نداشت، از جدول 1 استفاده کن
$discountRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '1']);
}
}
$discountRow->setRef($discountRef);
$this->entityManager->persist($discountRow);
}
// افزودن ردیف هزینه حمل
if ($shippingCost > 0) {
$shippingRow = new HesabdariRow();
$shippingRow->setDes('هزینه حمل و نقل');
$shippingRow->setBid($acc['bid']);
$shippingRow->setYear($acc['year']);
$shippingRow->setDoc($sellDoc);
$shippingRow->setBs($shippingCost);
$shippingRow->setBd(0);
// استفاده از جدول درآمد حمل کالا (کد 61) - مطابق با Controller فاکتور فروش
$shippingRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '61']);
if (!$shippingRef) {
// اگر جدول درآمد حمل کالا وجود نداشت، از جدول هزینه حمل کالا (کد 90) استفاده کن
$shippingRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '90']);
if (!$shippingRef) {
// اگر جدول 90 هم وجود نداشت، از جدول 1 استفاده کن
$shippingRef = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '1']);
}
}
$shippingRow->setRef($shippingRef);
$this->entityManager->persist($shippingRow);
}
// افزودن ردیف اصلی فاکتور (بدهکار به مشتری)
$mainRow = new HesabdariRow();
$mainRow->setDes('فاکتور فروش');
$mainRow->setBid($acc['bid']);
$mainRow->setYear($acc['year']);
$mainRow->setDoc($sellDoc);
$mainRow->setBs(0);
$mainRow->setBd($sumTotal + $sumTax + $shippingCost - $totalDiscountAmount);
// تنظیم مشتری
$mainRow->setPerson($preInvoice->getPerson());
// تنظیم مرجع حسابداری (جدول 3 برای بدهی به مشتری)
$ref = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']);
$mainRow->setRef($ref);
$this->entityManager->persist($mainRow);
// به‌روزرسانی مبلغ کل فاکتور فروش
$finalAmount = $sumTotal + $sumTax + $shippingCost - $totalDiscountAmount;
$sellDoc->setAmount($finalAmount);
$this->entityManager->persist($sellDoc);
// ایجاد لینک کوتاه
$sellDoc->setShortlink($this->provider->RandomString(8));
// به‌روزرسانی وضعیت پیش‌فاکتور
$preInvoice->setStatus('converted');
$this->entityManager->persist($preInvoice);
$this->entityManager->flush();
// ثبت لاگ برای عملیات پیش‌فاکتور
$this->log->insertPreInvoiceLog(
'پیش‌فاکتور',
'تبدیل پیش‌فاکتور شماره ' . $preInvoice->getCode() . ' به فاکتور فروش شماره ' . $sellDoc->getCode(),
$user,
$acc['bid']->getId()
);
// ثبت لاگ برای فاکتور فروش ایجاد شده
$this->log->insert(
'فاکتور فروش',
'فاکتور فروش شماره ' . $sellDoc->getCode() . ' از پیش‌فاکتور شماره ' . $preInvoice->getCode() . ' ایجاد شد',
$user,
$acc['bid']->getId(),
$sellDoc
);
return [
'success' => true,
'message' => 'پیش‌فاکتور با موفقیت به فاکتور فروش تبدیل شد',
'invoiceCode' => $sellDoc->getCode(),
'invoiceId' => $sellDoc->getId()
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => 'خطا در تبدیل پیش‌فاکتور: ' . $e->getMessage()
];
}
}
/**
* بررسی امکان تبدیل پیش‌فاکتور
*/
public function canConvert(PreInvoiceDoc $preInvoice): array
{
$errors = [];
// بررسی وضعیت پیش‌فاکتور
if ($preInvoice->getStatus() === 'converted') {
$errors[] = 'این پیش‌فاکتور قبلاً به فاکتور فروش تبدیل شده است';
}
// بررسی وجود آیتم‌ها
if ($preInvoice->getPreInvoiceItems()->count() === 0) {
$errors[] = 'پیش‌فاکتور باید حداقل یک آیتم داشته باشد';
}
// بررسی مشتری
if (!$preInvoice->getPerson()) {
$errors[] = 'مشتری برای پیش‌فاکتور تعیین نشده است';
}
return [
'canConvert' => empty($errors),
'errors' => $errors
];
}
}

View file

@ -59,6 +59,11 @@
<v-icon icon="mdi-file-edit"></v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" title="تبدیل به فاکتور فروش" @click="convertToInvoice(code)">
<template v-slot:prepend>
<v-icon color="blue" icon="mdi-file-document-arrow-right"></v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" :title="$t('dialog.delete')" @click="deleteItem(code)">
<template v-slot:prepend>
<v-icon color="deep-orange-accent-4" icon="mdi-trash-can"></v-icon>
@ -353,6 +358,30 @@ export default {
if (typeof str != "string") return false;
return !isNaN(str) && !isNaN(parseFloat(str));
},
async convertToInvoice(code) {
try {
this.loading = true;
const response = await axios.post(`/api/preinvoice/convert-to-invoice/${code}`);
if (response.data.success) {
this.showSnackbar(response.data.message, 'success');
// بارگذاری مجدد دادهها
this.loadData();
// نمایش اطلاعات فاکتور ایجاد شده
setTimeout(() => {
this.showSnackbar(`فاکتور فروش شماره ${response.data.invoiceCode} ایجاد شد`, 'info');
}, 1000);
} else {
this.showSnackbar(response.data.message || 'خطا در تبدیل پیش‌فاکتور', 'error');
}
} catch (error) {
console.error('Error converting preinvoice to invoice:', error);
this.showSnackbar(error.response?.data?.message || 'خطا در تبدیل پیش‌فاکتور', 'error');
} finally {
this.loading = false;
}
},
},
beforeMount() {
this.loadData();

View file

@ -12,6 +12,11 @@
</v-tooltip>
</template>
<v-spacer></v-spacer>
<v-tooltip text="تبدیل به فاکتور فروش" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-file-document-arrow-right" color="blue" @click="convertToInvoice()"></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="primary" @click="modal = true"></v-btn>
@ -174,6 +179,29 @@ export default defineComponent({
.then((response) => {
this.presellData = response.data;
});
},
async convertToInvoice() {
try {
const response = await axios.post(`/api/preinvoice/convert-to-invoice/${this.code}`);
if (response.data.success) {
// نمایش پیام موفقیت
this.$emit('show-snackbar', response.data.message, 'success');
// بستن دیالوگ
this.handleDialogClose(false);
// نمایش اطلاعات فاکتور ایجاد شده
setTimeout(() => {
this.$emit('show-snackbar', `فاکتور فروش شماره ${response.data.invoiceCode} ایجاد شد`, 'info');
}, 1000);
} else {
this.$emit('show-snackbar', response.data.message || 'خطا در تبدیل پیش‌فاکتور', 'error');
}
} catch (error) {
console.error('Error converting preinvoice to invoice:', error);
this.$emit('show-snackbar', error.response?.data?.message || 'خطا در تبدیل پیش‌فاکتور', 'error');
}
}
},
created() {