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';
}