update for Moadian plugin

This commit is contained in:
root 2025-07-20 18:48:50 +00:00
parent e2e1418137
commit 62375ab997
6 changed files with 969 additions and 491 deletions

View file

@ -19,7 +19,12 @@ use DateInterval;
class TaxSettingsController extends AbstractController
{
private $moadian_base_url = 'https://sandboxrc.tax.gov.ir/';
private function getMoadianBaseUrl(registryMGR $registryMGR): string
{
$sandboxMode = filter_var($registryMGR->get('system_settings', 'tax_system_sandbox_mode'), FILTER_VALIDATE_BOOLEAN);
return $sandboxMode ? 'https://sandboxrc.tax.gov.ir/' : 'https://rc.tax.gov.ir/';
}
#[Route('/api/plugins/tax/settings/get', name: 'plugin_tax_settings_get', methods: ['GET'])]
public function plugin_tax_settings_get(EntityManagerInterface $em, Access $access): JsonResponse
@ -216,8 +221,8 @@ class TaxSettingsController extends AbstractController
return $csr;
}
#[Route('/api/plugins/tax/settings/send-invoice', name: 'plugin_tax_settings_send_invoice', methods: ['POST'])]
public function plugin_tax_settings_send_invoice(Request $request, Access $access, Log $log, EntityManagerInterface $em): JsonResponse
#[Route('/api/plugins/tax/list/send-invoice', name: 'plugin_tax_list_send_invoice', methods: ['POST'])]
public function plugin_tax_list_send_invoice(Request $request, Access $access, Log $log, EntityManagerInterface $em): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
@ -225,9 +230,9 @@ class TaxSettingsController extends AbstractController
}
$params = $request->getPayload()->all();
$invoiceCode = $params['code'] ?? null;
$invoiceCodes = $params['codes'] ?? [];
if (!$invoiceCode) {
if (empty($invoiceCodes)) {
return $this->json([
'success' => false,
'message' => 'کد فاکتور الزامی است'
@ -239,20 +244,6 @@ class TaxSettingsController extends AbstractController
$userId = $user instanceof \App\Entity\User ? $user->getId() : null;
try {
$invoiceRepo = $em->getRepository(HesabdariDoc::class);
$invoice = $invoiceRepo->findOneBy([
'code' => $invoiceCode,
'bid' => $businessId,
'type' => 'sell'
]);
if (!$invoice) {
return $this->json([
'success' => false,
'message' => 'فاکتور مورد نظر یافت نشد'
]);
}
$taxRepo = $em->getRepository(PluginTaxsettingsKey::class);
$taxSettings = $taxRepo->findOneBy([
'business_id' => $businessId,
@ -266,28 +257,89 @@ class TaxSettingsController extends AbstractController
]);
}
$result = $this->saveInvoiceToSql($invoice, $taxSettings, $em, $businessId, $userId);
$invoiceRepo = $em->getRepository(HesabdariDoc::class);
$results = [];
$successCount = 0;
$errorCount = 0;
$processedCodes = [];
if ($result['success']) {
$log->insert('اضافه به لیست ارسال', 'فاکتور ' . $invoiceCode . ' به لیست ارسال به سامانه مودیان اضافه شد', $this->getUser(), $businessId);
foreach ($invoiceCodes as $invoiceCode) {
$invoiceCode = trim($invoiceCode);
if (empty($invoiceCode)) {
continue;
}
return $this->json([
'success' => true,
'message' => 'فاکتور با موفقیت به لیست ارسال اضافه شد',
'data' => $result['data'] ?? null
]);
} else {
return $this->json([
'success' => false,
'message' => $result['message'] ?? 'خطا در اضافه کردن به لیست ارسال'
]);
try {
$invoice = $invoiceRepo->findOneBy([
'code' => $invoiceCode,
'bid' => $businessId,
'type' => 'sell'
]);
if (!$invoice) {
$results[] = [
'code' => $invoiceCode,
'success' => false,
'message' => 'فاکتور مورد نظر یافت نشد'
];
$errorCount++;
continue;
}
$result = $this->saveInvoiceToSql($invoice, $taxSettings, $em, $businessId, $userId);
if ($result['success']) {
$results[] = [
'code' => $invoiceCode,
'success' => true,
'message' => 'فاکتور با موفقیت به لیست ارسال اضافه شد',
'data' => $result['data'] ?? null
];
$successCount++;
$processedCodes[] = $invoiceCode;
} else {
$results[] = [
'code' => $invoiceCode,
'success' => false,
'message' => $result['message'] ?? 'خطا در اضافه کردن به لیست ارسال'
];
$errorCount++;
}
} catch (\Exception $e) {
$results[] = [
'code' => $invoiceCode,
'success' => false,
'message' => 'خطا در پردازش فاکتور: ' . $e->getMessage()
];
$errorCount++;
}
}
if (!empty($processedCodes)) {
$codesText = implode(', ', $processedCodes);
$log->insert('اضافه به لیست ارسال', 'فاکتورهای ' . $codesText . ' به لیست ارسال به سامانه مودیان اضافه شد', $this->getUser(), $businessId);
}
$totalProcessed = count($invoiceCodes);
$message = "پردازش {$totalProcessed} فاکتور تکمیل شد. موفق: {$successCount}, ناموفق: {$errorCount}";
return $this->json([
'success' => true,
'message' => $message,
'summary' => [
'total' => $totalProcessed,
'success' => $successCount,
'error' => $errorCount
],
'results' => $results
]);
} catch (\Exception $e) {
$log->insert('خطا در اضافه به لیست ارسال', 'خطا در اضافه کردن فاکتور ' . $invoiceCode . ' به لیست ارسال: ' . $e->getMessage(), $this->getUser(), $businessId);
$log->insert('خطا در اضافه به لیست ارسال', 'خطا در پردازش فاکتورها: ' . $e->getMessage(), $this->getUser(), $businessId);
return $this->json([
'success' => false,
'message' => 'خطا در اضافه کردن به لیست ارسال: ' . $e->getMessage()
'message' => 'خطا در پردازش فاکتورها: ' . $e->getMessage()
]);
}
}
@ -663,7 +715,7 @@ class TaxSettingsController extends AbstractController
}
#[Route('/api/plugins/tax/invoice/send/{id}', name: 'plugin_tax_invoice_send', methods: ['POST'])]
public function sendTaxInvoice(int $id, Access $access, Log $log, EntityManagerInterface $em): JsonResponse
public function sendTaxInvoice(int $id, Access $access, Log $log, EntityManagerInterface $em, registryMGR $registryMGR): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
@ -722,7 +774,7 @@ class TaxSettingsController extends AbstractController
$privateKey,
'',
$username,
$this->moadian_base_url
$this->getMoadianBaseUrl($registryMGR)
);
$serverInfo = $moadian->getServerInformation();
@ -741,7 +793,7 @@ class TaxSettingsController extends AbstractController
$privateKey,
$taxOrgKeyId,
$username,
$this->moadian_base_url
$this->getMoadianBaseUrl($registryMGR)
);
$token = $moadian->login();
@ -880,7 +932,7 @@ class TaxSettingsController extends AbstractController
}
#[Route('/api/plugins/tax/inquire-status', name: 'plugin_tax_inquire_status', methods: ['POST'])]
public function inquireInvoiceStatus(Request $request, Access $access, EntityManagerInterface $em): JsonResponse
public function inquireInvoiceStatus(Request $request, Access $access, EntityManagerInterface $em, registryMGR $registryMGR): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
@ -922,7 +974,7 @@ class TaxSettingsController extends AbstractController
$privateKey,
$taxOrgKeyId,
$username,
$this->moadian_base_url
$this->getMoadianBaseUrl($registryMGR)
);
$token = $moadian->login();
@ -1180,6 +1232,238 @@ class TaxSettingsController extends AbstractController
}
}
#[Route('/api/plugins/tax/invoice/send-bulk', name: 'plugin_tax_invoice_send_bulk', methods: ['POST'])]
public function sendBulkTaxInvoices(Request $request, Access $access, Log $log, EntityManagerInterface $em, registryMGR $registryMGR): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.');
}
$params = $request->getPayload()->all();
$invoiceIds = $params['ids'] ?? [];
if (empty($invoiceIds)) {
return $this->json([
'success' => false,
'message' => 'شناسه فاکتورهای مالیاتی الزامی است'
]);
}
$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' => 'تنظیمات مالیاتی تکمیل نشده است. لطفاً ابتدا تنظیمات را تکمیل کنید.'
]);
}
try {
$username = $taxSettings->getTaxMemoryId();
$privateKey = $taxSettings->getPrivateKey();
if (!$username || !$privateKey) {
return $this->json([
'success' => false,
'message' => 'تنظیمات مالیاتی تکمیل نشده است. لطفاً ابتدا تنظیمات را تکمیل کنید.'
]);
}
$moadian = new \SnappMarketPro\Moadian\Moadian(
'',
$privateKey,
'',
$username,
$this->getMoadianBaseUrl($registryMGR)
);
$serverInfo = $moadian->getServerInformation();
if (!isset($serverInfo['result']['data']['publicKeys'][0])) {
return $this->json([
'success' => false,
'message' => 'خطا در دریافت اطلاعات سرور مودیان'
]);
}
$taxOrgPublicKey = $serverInfo['result']['data']['publicKeys'][0]['key'];
$taxOrgKeyId = $serverInfo['result']['data']['publicKeys'][0]['id'];
$moadian = new \SnappMarketPro\Moadian\Moadian(
$taxOrgPublicKey,
$privateKey,
$taxOrgKeyId,
$username,
$this->getMoadianBaseUrl($registryMGR)
);
$token = $moadian->login();
if (!$token) {
return $this->json([
'success' => false,
'message' => 'خطا در اتصال به سامانه مودیان، لطفاً تنظیمات را بررسی کنید.'
]);
}
$moadian->setToken($token);
$taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class);
$results = [];
$successCount = 0;
$errorCount = 0;
$processedCodes = [];
foreach ($invoiceIds as $id) {
try {
$taxInvoice = $taxInvoiceRepo->findOneBy([
'id' => $id,
'business' => $businessId
]);
if (!$taxInvoice) {
$results[] = [
'id' => $id,
'code' => null,
'success' => false,
'message' => 'فاکتور مالیاتی مورد نظر یافت نشد'
];
$errorCount++;
continue;
}
$invoiceStatus = $taxInvoice->getStatus();
if ($invoiceStatus !== 'pending' && $invoiceStatus !== 'error') {
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode(),
'success' => false,
'message' => 'فقط فاکتورهای ارسال نشده یا خطا دار قابل ارسال هستند'
];
$errorCount++;
continue;
}
$invoice = $taxInvoice->getInvoice();
if (!$invoice) {
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode(),
'success' => false,
'message' => 'فاکتور معتبر نیست'
];
$errorCount++;
continue;
}
$validationResult = $this->validateInvoiceForTax($invoice);
if (!$validationResult['valid']) {
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode(),
'success' => false,
'message' => $validationResult['message']
];
$errorCount++;
continue;
}
$invoiceDto = $this->buildInvoiceDto($invoice, $moadian, $taxSettings->getEconomicCode());
if (!$invoiceDto) {
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode(),
'success' => false,
'message' => 'خطا در آماده‌سازی فاکتور: خطا در ساخت DTO فاکتور'
];
$errorCount++;
continue;
}
$response = $moadian->sendInvoices([$invoiceDto]);
if (isset($response['result'][0]['referenceNumber']) && !empty($response['result'])) {
$taxInvoice->setStatus('sent');
$taxInvoice->setTaxSystemInvoiceNumber($response['result'][0]['referenceNumber']);
$taxInvoice->setSentAt(new \DateTimeImmutable());
$em->persist($taxInvoice);
$em->flush();
if ($invoiceStatus === 'error') {
$taxInvoice->setInvoiceType('اصلاحی');
$em->persist($taxInvoice);
$em->flush();
}
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode(),
'success' => true,
'message' => 'فاکتور با موفقیت ارسال شد',
'referenceNumber' => $response['result'][0]['referenceNumber'] ?? null
];
$successCount++;
$processedCodes[] = $taxInvoice->getInvoiceCode();
} else {
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode(),
'success' => false,
'message' => 'خطا در ارسال فاکتور: ' . ($response['result'][0]['error'] ?? 'خطای نامشخص')
];
$errorCount++;
}
} catch (\Exception $e) {
$results[] = [
'id' => $id,
'code' => $taxInvoice->getInvoiceCode() ?? null,
'success' => false,
'message' => 'خطا در پردازش فاکتور: ' . $e->getMessage()
];
$errorCount++;
}
}
if (!empty($processedCodes)) {
$codesText = implode(', ', $processedCodes);
$log->insert(
'ارسال گروهی فاکتورهای مالیاتی',
'فاکتورهای مالیاتی ' . $codesText . ' به سامانه مودیان ارسال شد.',
$this->getUser(),
$businessId
);
}
$totalProcessed = count($invoiceIds);
$message = "پردازش {$totalProcessed} فاکتور مالیاتی تکمیل شد. موفق: {$successCount}, ناموفق: {$errorCount}";
return $this->json([
'success' => $successCount > 0,
'message' => $message,
'summary' => [
'total' => $totalProcessed,
'success' => $successCount,
'error' => $errorCount
],
'results' => $results
]);
} catch (\Exception $e) {
return $this->json([
'success' => false,
'message' => 'خطا در ارسال گروهی فاکتورهای مالیاتی: ' . $e->getMessage()
]);
}
}
}

View file

@ -57,6 +57,7 @@ final class RegistrySettingsController extends AbstractController
'appUrl' => $registryMGR->get('system', 'appUrl'),
'appSlogan' => $registryMGR->get('system', 'appSlogan'),
'verifyMobileViaSms' => filter_var($registryMGR->get('system', 'verifyMobileViaSms'), FILTER_VALIDATE_BOOLEAN),
'taxSystemSandboxMode' => filter_var($registryMGR->get($rootSystem, 'tax_system_sandbox_mode'), FILTER_VALIDATE_BOOLEAN),
// تنظیمات FTP
'ftpEnabled' => filter_var($registryMGR->get($rootSystem, 'ftp_enabled'), FILTER_VALIDATE_BOOLEAN),
'ftpHost' => $registryMGR->get($rootSystem, 'ftp_host') ?: '',
@ -96,6 +97,7 @@ final class RegistrySettingsController extends AbstractController
$registryMGR->update('system', 'appUrl', $data['appUrl'] ?? '');
$registryMGR->update('system', 'appSlogan', $data['appSlogan'] ?? '');
$registryMGR->update('system', 'verifyMobileViaSms', $data['verifyMobileViaSms'] ? '1' : '0');
$registryMGR->update($rootSystem, 'tax_system_sandbox_mode', $data['taxSystemSandboxMode'] ? '1' : '0');
// ذخیره تنظیمات FTP
$registryMGR->update($rootSystem, 'ftp_enabled', $data['ftpEnabled'] ? '1' : '0');
$registryMGR->update($rootSystem, 'ftp_host', $data['ftpHost'] ?? '');

View file

@ -571,7 +571,7 @@ const fa_lang = {
status: "وضعیت",
actions: "عملیات"
},
send_to_tax_system: "ارسال به سامانه مودیان",
send_to_tax_system: "ارسال به کارپوشه مودیان",
tax_send_success: "فاکتور با موفقیت به سامانه مودیان ارسال شد.",
tax_send_error: "ارسال به سامانه مودیان با خطا مواجه شد.",
},

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,18 @@
<v-btn v-bind="props" icon="mdi-delete" color="danger" @click="deleteItems()"></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()"
:disabled="itemsSelected.length === 0"
:loading="bulkLoading"
></v-btn>
</template>
</v-tooltip>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="" color="green">
@ -364,6 +376,7 @@ export default defineComponent({
searchValue: '',
types: [],
loading: false,
bulkLoading: false,
items: [],
total: 0,
expanded: [],
@ -726,17 +739,31 @@ export default defineComponent({
async sendToTaxSystem(code) {
this.loading = true;
try {
const response = await axios.post('/api/plugins/tax/settings/send-invoice', { code });
const response = await axios.post('/api/plugins/tax/list/send-invoice', { codes: [code] });
if (response.data.success) {
Swal.fire({
text: this.$t('dialog.tax_send_success'),
icon: 'success',
confirmButtonText: this.$t('dialog.ok')
}).then((result) => {
if (result.isConfirmed) {
this.$router.push('/acc/plugins/tax/invoices/list');
}
});
const results = response.data.results;
const invoiceResult = results.find(r => r.code === code);
if (invoiceResult && invoiceResult.success) {
Swal.fire({
text: this.$t('dialog.tax_send_success'),
icon: 'success',
confirmButtonText: this.$t('dialog.ok')
}).then((result) => {
if (result.isConfirmed) {
this.$router.push('/acc/plugins/tax/invoices/list');
}
});
} else {
const errorMessage = invoiceResult?.message || response.data.message || this.$t('dialog.tax_send_error');
Swal.fire({
text: errorMessage,
icon: 'error',
confirmButtonText: this.$t('dialog.ok')
});
}
} else {
Swal.fire({
text: response.data.message || this.$t('dialog.tax_send_error'),
@ -754,6 +781,96 @@ export default defineComponent({
this.loading = false;
}
},
async sendBulkToTaxSystem() {
if (this.itemsSelected.length === 0) {
Swal.fire({
text: 'هیچ فاکتوری انتخاب نشده است.',
icon: 'warning',
confirmButtonText: 'قبول'
});
return;
}
const selectedCount = this.itemsSelected.length;
const result = await Swal.fire({
title: 'ارسال گروهی به کارپوشه مودیان',
text: `آیا می‌خواهید ${selectedCount} فاکتور انتخاب شده را به کارپوشه مودیان ارسال کنید؟`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'بله، ارسال کن',
cancelButtonText: 'انصراف',
confirmButtonColor: '#ff9800',
cancelButtonColor: '#6c757d'
});
if (result.isConfirmed) {
this.bulkLoading = true;
try {
const response = await axios.post('/api/plugins/tax/list/send-invoice', {
codes: this.itemsSelected
});
if (response.data.success) {
const summary = response.data.summary;
const results = response.data.results;
let successCount = 0;
let errorCount = 0;
let 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${errorMessages.slice(0, 5).join('\n')}`;
if (errorMessages.length > 5) {
message += `\${errorMessages.length - 5} فاکتور دیگر...`;
}
}
Swal.fire({
title: 'نتیجه ارسال گروهی',
html: message.replace(/\n/g, '<br>'),
icon: successCount > 0 ? 'success' : 'error',
confirmButtonText: 'قبول',
width: '600px'
}).then((result) => {
if (result.isConfirmed && successCount > 0) {
this.$router.push('/acc/plugins/tax/invoices/list');
}
});
this.itemsSelected = [];
} else {
Swal.fire({
text: response.data.message || 'خطا در ارسال گروهی فاکتورها',
icon: 'error',
confirmButtonText: 'قبول'
});
}
} catch (error) {
Swal.fire({
text: error.response?.data?.message || 'خطا در ارسال گروهی فاکتورها',
icon: 'error',
confirmButtonText: 'قبول'
});
} finally {
this.bulkLoading = false;
}
}
},
},
created() {
this.loadColumnSettings();

View file

@ -33,6 +33,7 @@ export default defineComponent({
ftpUsername: '',
ftpPassword: '',
ftpPath: '',
taxSystemSandboxMode: false,
},
dialogVisible: false,
dialogMessage: '',
@ -56,6 +57,7 @@ export default defineComponent({
appUrl: data.appUrl || '',
appSlogan: data.appSlogan || '',
verifyMobileViaSms: data.verifyMobileViaSms || false,
taxSystemSandboxMode: data.taxSystemSandboxMode || false,
};
this.checkFreeAccounting();
}
@ -86,6 +88,7 @@ export default defineComponent({
appUrl: this.settings.appUrl,
appSlogan: this.settings.appSlogan,
verifyMobileViaSms: this.settings.verifyMobileViaSms,
taxSystemSandboxMode: this.settings.taxSystemSandboxMode,
};
try {
@ -318,6 +321,13 @@ export default defineComponent({
color="primary"
></v-switch>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-switch
v-model="settings.taxSystemSandboxMode"
label="فعال کردن حالت سند باکس سامانه مالیاتی"
color="primary"
></v-switch>
</v-col>
<v-row>
<v-col cols="12" sm="12" md="4">
<v-text-field