From 140da029a13547240eb06c9adf6d9ca225fd62d2 Mon Sep 17 00:00:00 2001 From: Gloomy Date: Sun, 3 Aug 2025 11:15:03 +0000 Subject: [PATCH] update for Moadian Plugin --- .../Plugins/TaxSettingsController.php | 156 ++++++++++++++- .../views/acc/plugins/tax/invoices/list.vue | 178 +++++++++++++++++- 2 files changed, 326 insertions(+), 8 deletions(-) diff --git a/hesabixCore/src/Controller/Plugins/TaxSettingsController.php b/hesabixCore/src/Controller/Plugins/TaxSettingsController.php index 7a96ce3..7049c28 100644 --- a/hesabixCore/src/Controller/Plugins/TaxSettingsController.php +++ b/hesabixCore/src/Controller/Plugins/TaxSettingsController.php @@ -1172,10 +1172,24 @@ class TaxSettingsController extends AbstractController break; } } - + if ($buyerPerson) { $buyerNationalId = $buyerPerson->getShenasemeli(); $buyerEconomicCode = $buyerPerson->getCodeeghtesadi(); + + if (empty($buyerNationalId) || trim($buyerNationalId) === '') { + $buyerNationalId = null; + } + + if (empty($buyerEconomicCode) || trim($buyerEconomicCode) === '') { + $buyerEconomicCode = null; + } + } + + $personType = 1; + + if (count_chars($buyerNationalId ) == 11) { + $personType = 2; } $dateTime = new DateTime(); @@ -1189,7 +1203,7 @@ class TaxSettingsController extends AbstractController ->setInp(1) ->setIns(1) ->setTins($taxId) - ->setTob(1) + ->setTob($personType) ->setBid($buyerNationalId) ->setTinb($buyerEconomicCode) ->setSbc(null) @@ -1504,5 +1518,143 @@ class TaxSettingsController extends AbstractController } } + #[Route('/api/plugins/tax/invoice/validate-buyer-info/{id}', name: 'plugin_tax_invoice_validate_buyer_info', methods: ['POST'])] + public function validateBuyerInfo(int $id, Access $access, Log $log, EntityManagerInterface $em): JsonResponse + { + $acc = $access->hasRole('plugTaxSettings'); + if (!$acc) { + throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.'); + } + + $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; + $user = $this->getUser(); + $userId = $user instanceof \App\Entity\User ? $user->getId() : null; + + $repo = $em->getRepository(PluginTaxsettingsKey::class); + $taxSettings = $repo->findOneBy(['business_id' => $businessId, 'user_id' => $userId]); + + if (!$taxSettings || !$taxSettings->getPrivateKey() || !$taxSettings->getTaxMemoryId()) { + return $this->json([ + 'success' => false, + 'message' => 'تنظیمات مالیاتی تکمیل نشده است. لطفاً ابتدا تنظیمات را تکمیل کنید.' + ]); + } + + $taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class); + $taxInvoice = $taxInvoiceRepo->findOneBy([ + 'id' => $id, + 'business' => $businessId + ]); + + if (!$taxInvoice) { + return $this->json([ + 'success' => false, + 'message' => 'فاکتور مالیاتی یافت نشد.' + ]); + } + + $invoice = $taxInvoice->getInvoice(); + if (!$invoice) { + return $this->json([ + 'success' => false, + 'message' => 'فاکتور یافت نشد.' + ]); + } + + if ($invoice->getBid()->getId() != $businessId) { + return $this->json([ + 'success' => false, + 'message' => 'فاکتور متعلق به این کسب و کار نیست.' + ]); + } + + $buyerInfo = $this->validateBuyerEconomicInfo($invoice); + + if (!$buyerInfo['is_valid']) { + return $this->json([ + 'success' => false, + 'message' => 'اطلاعات اقتصادی خریدار ناقص است.', + 'buyer_info' => $buyerInfo, + 'can_proceed' => false + ]); + } + + return $this->json([ + 'success' => true, + 'message' => 'اطلاعات اقتصادی خریدار کامل است.', + 'buyer_info' => $buyerInfo, + 'can_proceed' => true + ]); + } + + /** + * بررسی اطلاعات اقتصادی خریدار + */ + private function validateBuyerEconomicInfo($invoice): array + { + $buyerPerson = null; + $buyerNationalId = null; + $buyerEconomicCode = null; + $missingFields = []; + + // دریافت شخص خریدار از ردیف‌های فاکتور + foreach ($invoice->getHesabdariRows() as $row) { + if ($row->getPerson()) { + $buyerPerson = $row->getPerson(); + break; + } + } + + if (!$buyerPerson) { + return [ + 'is_valid' => false, + 'message' => 'خریدار در فاکتور مشخص نشده است.', + 'buyer_name' => null, + 'national_id' => null, + 'economic_code' => null, + 'missing_fields' => ['buyer_not_found'] + ]; + } + + $buyerNationalId = $buyerPerson->getShenasemeli(); + $buyerEconomicCode = $buyerPerson->getCodeeghtesadi(); + + // بررسی شناسه ملی + if (empty($buyerNationalId) || trim($buyerNationalId) === '') { + $missingFields[] = 'national_id'; + } + + // بررسی کد اقتصادی + if (empty($buyerEconomicCode) || trim($buyerEconomicCode) === '') { + $missingFields[] = 'economic_code'; + } + + $result = [ + 'is_valid' => empty($missingFields), + 'buyer_name' => $buyerPerson->getNikename(), + 'buyer_id' => $buyerPerson->getId(), + 'buyer_code' => $buyerPerson->getCode(), + 'national_id' => $buyerNationalId, + 'economic_code' => $buyerEconomicCode, + 'missing_fields' => $missingFields + ]; + + if (!empty($missingFields)) { + $missingFieldsText = []; + if (in_array('national_id', $missingFields)) { + $missingFieldsText[] = 'شناسه ملی'; + } + if (in_array('economic_code', $missingFields)) { + $missingFieldsText[] = 'کد اقتصادی'; + } + + $result['message'] = 'اطلاعات اقتصادی خریدار ناقص است. فیلدهای زیر تکمیل نشده‌اند: ' . implode('، ', $missingFieldsText); + } else { + $result['message'] = 'اطلاعات اقتصادی خریدار کامل است.'; + } + + return $result; + } + } \ No newline at end of file diff --git a/webUI/src/views/acc/plugins/tax/invoices/list.vue b/webUI/src/views/acc/plugins/tax/invoices/list.vue index 81bd172..823116b 100644 --- a/webUI/src/views/acc/plugins/tax/invoices/list.vue +++ b/webUI/src/views/acc/plugins/tax/invoices/list.vue @@ -253,6 +253,7 @@ export default { return { loading: false, bulkLoading: false, + checkLoading: false, selectedInvoices: [], invoices: [], snackbar: { @@ -342,7 +343,7 @@ export default { return 'خطا'; case 'FAILED': return 'خطا'; - case 'ACCEPTED': + case 'SUCCESS': return 'تایید شده'; default: return 'نامشخص'; @@ -360,7 +361,7 @@ export default { return 'grey'; case 'FAILED': return 'error'; - case 'ACCEPTED': + case 'SUCCESS': return 'success'; default: return 'primary'; @@ -533,13 +534,62 @@ export default { }, async performSend(item) { item.sending = true; + try { + const validateResponse = await axios.post(`/api/plugins/tax/invoice/validate-buyer-info/${item.id}`); + + if (!validateResponse.data.success && !validateResponse.data.can_proceed) { + if (validateResponse.data.buyer_info) { + const buyerInfo = validateResponse.data.buyer_info; + let message = `اطلاعات اقتصادی خریدار ناقص است.\n\n`; + message += `خریدار: ${buyerInfo.buyer_name || 'نامشخص'}`; + + Swal.fire({ + title: 'اطلاعات اقتصادی ناقص', + html: message.replace(/\n/g, '
'), + icon: 'warning', + confirmButtonText: 'ارسال بدون اطلاعات خریدار', + showCancelButton: true, + cancelButtonText: 'ویرایش خریدار', + showDenyButton: true, + denyButtonText: 'انصراف' + }).then((result) => { + if (result.isConfirmed) { + this.sendWithoutBuyerInfo(item); + } else if (result.dismiss === Swal.DismissReason.cancel && buyerInfo.buyer_code) { + this.$router.push(`/acc/persons/mod/${buyerInfo.buyer_code}`); + } + }); + } else { + Swal.fire({ + title: 'خطا در بررسی اطلاعات', + text: validateResponse.data.message || 'خطا در بررسی اطلاعات اقتصادی خریدار', + icon: 'error', + confirmButtonText: 'باشه' + }); + } + return; + } else if (validateResponse.data.can_proceed) { + this.sendWithoutBuyerInfo(item); + } + } catch (error) { + Swal.fire({ + title: 'خطا در ارسال فاکتور', + text: 'خطا در ارسال فاکتور: ' + (error.response?.data?.message || error.message), + icon: 'error', + confirmButtonText: 'باشه' + }); + } finally { + item.sending = false; + } + }, + async sendWithoutBuyerInfo(item) { try { const response = await axios.post(`/api/plugins/tax/invoice/send/${item.id}`); if (response.data.success) { Swal.fire({ title: 'ارسال موفق', - text: 'فاکتور با موفقیت به سامانه مودیان مالیاتی ارسال شد', + text: 'فاکتور بدون اطلاعات خریدار به سامانه مودیان مالیاتی ارسال شد', icon: 'success', confirmButtonText: 'باشه' }); @@ -559,8 +609,6 @@ export default { icon: 'error', confirmButtonText: 'باشه' }); - } finally { - item.sending = false; } }, sendBulkInvoices() { @@ -589,8 +637,65 @@ export default { async performBulkSend(selectedItems) { this.bulkLoading = true; try { + const validationPromises = selectedItems.map(item => + axios.post(`/api/plugins/tax/invoice/validate-buyer-info/${item.id}`) + ); + + const validationResults = await Promise.all(validationPromises); + + const invalidInvoices = []; + const validInvoices = []; + + validationResults.forEach((result, index) => { + if (!result.data.success) { + invalidInvoices.push({ + ...selectedItems[index], + buyerInfo: result.data.buyer_info + }); + } else { + validInvoices.push(selectedItems[index]); + } + }); + + if (invalidInvoices.length > 0) { + let message = `تعداد ${invalidInvoices.length} فاکتور دارای اطلاعات اقتصادی ناقص هستند.\n\n`; + message += `خریداران دارای مشکل:\n`; + + const uniqueBuyers = new Map(); + invalidInvoices.forEach(invoice => { + const buyerInfo = invoice.buyerInfo; + if (buyerInfo && buyerInfo.buyer_name) { + uniqueBuyers.set(buyerInfo.buyer_name, true); + } + }); + + uniqueBuyers.forEach((value, buyerName) => { + message += `• ${buyerName}\n`; + }); + + Swal.fire({ + title: 'اطلاعات اقتصادی ناقص', + html: message.replace(/\n/g, '
'), + icon: 'warning', + confirmButtonText: 'ارسال بدون اطلاعات خریدار', + showCancelButton: true, + cancelButtonText: 'مشاهده لیست اشخاص', + showDenyButton: true, + denyButtonText: 'انصراف' + }).then((result) => { + if (result.isConfirmed) { + this.sendBulkWithoutBuyerInfo(selectedItems); + } else if (result.dismiss === Swal.DismissReason.cancel) { + this.$router.push('/acc/persons/list'); + } + }); + + this.bulkLoading = false; + return; + } + const response = await axios.post('/api/plugins/tax/invoice/send-bulk', { - ids: selectedItems.map(item => item.id) + ids: validInvoices.map(item => item.id) }); if (response.data.success) { @@ -649,6 +754,67 @@ export default { this.bulkLoading = false; } }, + async sendBulkWithoutBuyerInfo(selectedItems) { + try { + const response = await axios.post('/api/plugins/tax/invoice/send-bulk', { + ids: selectedItems.map(item => item.id), + skip_buyer_validation: true + }); + + if (response.data.success) { + const summary = response.data.summary; + const results = response.data.results; + + let successCount = 0; + let errorCount = 0; + const errorMessages = []; + + results.forEach(result => { + if (result.success) { + successCount++; + } else { + errorCount++; + errorMessages.push(`${result.code}: ${result.message}`); + } + }); + + let message = `پردازش ${summary.total} فاکتور مالیاتی بدون اطلاعات خریدار تکمیل شد.\n\n`; + message += `✅ موفق: ${successCount} فاکتور\n`; + message += `❌ ناموفق: ${errorCount} فاکتور`; + + if (errorCount > 0 && errorMessages.length > 0) { + message += `\n\nفاکتورهای ناموفق:\n${errorMessages.slice(0, 5).join('\n')}`; + if (errorMessages.length > 5) { + message += `\nو ${errorMessages.length - 5} فاکتور دیگر...`; + } + } + + Swal.fire({ + title: successCount > 0 ? 'ارسال گروهی تکمیل شد' : 'خطا در ارسال گروهی', + html: message.replace(/\n/g, '
'), + icon: successCount > 0 ? 'success' : 'error', + confirmButtonText: 'باشه' + }); + + this.selectedInvoices = []; + this.loadData(); + } else { + Swal.fire({ + title: 'خطا در ارسال گروهی', + text: response.data.message || 'خطا در ارسال گروهی فاکتورها', + icon: 'error', + confirmButtonText: 'باشه' + }); + } + } catch (error) { + Swal.fire({ + title: 'خطا در ارسال گروهی', + text: 'خطا در ارسال گروهی فاکتورها: ' + (error.response?.data?.message || error.message), + icon: 'error', + confirmButtonText: 'باشه' + }); + } + }, isItemSelectable(item) { return item.status === 'pending' || item.status === 'error'; }