diff --git a/.env.e2e.example b/.env.e2e.example index 602a0fdd..159a789d 100644 --- a/.env.e2e.example +++ b/.env.e2e.example @@ -4,3 +4,4 @@ NFSE_E2E_BASE_URL="http://localhost:8082" NFSE_E2E_EMAIL="" NFSE_E2E_PASSWORD="" +NFSE_E2E_REAL_EMIT_FLOW="0" diff --git a/.gitignore b/.gitignore index 1fedc203..b885ff3d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /node_modules/ /playwright-report/ /test-results/ +.env.e2e diff --git a/Database/Migrations/2026_03_29_000001_add_codigo_tributacao_nacional_to_nfse_company_services_table.php b/Database/Migrations/2026_03_29_000001_add_codigo_tributacao_nacional_to_nfse_company_services_table.php new file mode 100644 index 00000000..73119102 --- /dev/null +++ b/Database/Migrations/2026_03_29_000001_add_codigo_tributacao_nacional_to_nfse_company_services_table.php @@ -0,0 +1,44 @@ +string('codigo_tributacao_nacional', 6) + ->nullable() + ->after('item_lista_servico'); + }); + } + + public function down(): void + { + if (!Schema::hasTable('nfse_company_services')) { + return; + } + + if (!Schema::hasColumn('nfse_company_services', 'codigo_tributacao_nacional')) { + return; + } + + Schema::table('nfse_company_services', function (Blueprint $table): void { + $table->dropColumn('codigo_tributacao_nacional'); + }); + } +}; diff --git a/Http/Controllers/CompanyServiceController.php b/Http/Controllers/CompanyServiceController.php index da9d6364..6b2e39a6 100644 --- a/Http/Controllers/CompanyServiceController.php +++ b/Http/Controllers/CompanyServiceController.php @@ -119,10 +119,6 @@ public function update(Request $request, CompanyService $service): RedirectRespo $service->update($validated); - if ($service->is_default) { - $this->syncDefaultServiceSettings($service); - } - return redirect()->route('nfse.settings.edit', ['tab' => 'services']) ->with('success', trans('nfse::general.settings.services.service_updated')); } @@ -244,18 +240,5 @@ private function setDefaultService(CompanyService $service): void ->update(['is_default' => false]); $service->update(['is_default' => true]); - - $this->syncDefaultServiceSettings($service); - } - - private function syncDefaultServiceSettings(CompanyService $service): void - { - setting([ - 'nfse.item_lista_servico' => (string) $service->item_lista_servico, - 'nfse.codigo_tributacao_nacional' => (string) $service->codigo_tributacao_nacional, - 'nfse.aliquota' => number_format((float) $service->aliquota, 2, '.', ''), - ]); - - setting()->save(); } } diff --git a/Http/Controllers/InvoiceController.php b/Http/Controllers/InvoiceController.php index 027b3f97..1cc06bb6 100644 --- a/Http/Controllers/InvoiceController.php +++ b/Http/Controllers/InvoiceController.php @@ -21,6 +21,7 @@ use LibreCodeCoop\NfsePHP\Exception\SecretStoreException; use LibreCodeCoop\NfsePHP\Http\NfseClient; use LibreCodeCoop\NfsePHP\SecretStore\OpenBaoSecretStore; +use Modules\Nfse\Models\CompanyService; use Modules\Nfse\Models\NfseReceipt; use Modules\Nfse\Support\VaultConfig; @@ -66,6 +67,7 @@ public function show(Invoice $invoice): \Illuminate\View\View public function emit(Invoice $invoice): RedirectResponse { $this->ensureInvoiceRelationsLoaded($invoice); + $defaultService = $this->resolveDefaultCompanyService($invoice); $readiness = $this->emissionReadiness(); @@ -79,19 +81,34 @@ public function emit(Invoice $invoice): RedirectResponse $sandbox = (bool) setting('nfse.sandbox_mode', true); $tomadorDocument = $this->normalizedTomadorDocument($invoice->contact?->tax_number); $opcaoSimplesNacional = $this->normalizedOpcaoSimplesNacional(); + $federalPayload = $this->federalPayloadValues((float) $invoice->amount); $dps = new DpsData( cnpjPrestador: $cnpj, municipioIbge: $ibge, - itemListaServico: setting('nfse.item_lista_servico', '0107'), - codigoTributacaoNacional: $this->nationalTaxCode(), + itemListaServico: $this->itemListaServico($defaultService), + codigoTributacaoNacional: $this->nationalTaxCode($defaultService), valorServico: number_format((float) $invoice->amount, 2, '.', ''), - aliquota: $this->normalizedAliquota(), + aliquota: $this->normalizedAliquota($defaultService), discriminacao: $this->buildDiscriminacao($invoice), documentoTomador: $tomadorDocument, nomeTomador: $invoice->contact?->name ?? '', opcaoSimplesNacional: $opcaoSimplesNacional, tipoAmbiente: $sandbox ? 2 : 1, + serie: $this->dpsSerie($invoice), + numeroDps: $this->dpsNumber($invoice), + dataCompetencia: $this->competenceDate($invoice), + indicadorTributacao: $federalPayload['indicadorTributacao'], + federalPiscofinsSituacaoTributaria: $federalPayload['federalPiscofinsSituacaoTributaria'], + federalPiscofinsTipoRetencao: $federalPayload['federalPiscofinsTipoRetencao'], + federalPiscofinsBaseCalculo: $federalPayload['federalPiscofinsBaseCalculo'], + federalPiscofinsAliquotaPis: $federalPayload['federalPiscofinsAliquotaPis'], + federalPiscofinsValorPis: $federalPayload['federalPiscofinsValorPis'], + federalPiscofinsAliquotaCofins: $federalPayload['federalPiscofinsAliquotaCofins'], + federalPiscofinsValorCofins: $federalPayload['federalPiscofinsValorCofins'], + federalValorIrrf: $federalPayload['federalValorIrrf'], + federalValorCsll: $federalPayload['federalValorCsll'], + federalValorCp: $federalPayload['federalValorCp'], ); $this->safeLogInfo('NFS-e emission payload', [ @@ -221,6 +238,7 @@ public function refreshAll(): RedirectResponse public function reemit(Invoice $invoice): RedirectResponse { $this->ensureInvoiceRelationsLoaded($invoice); + $defaultService = $this->resolveDefaultCompanyService($invoice); $receipt = $this->findReceiptForInvoice($invoice); @@ -239,19 +257,34 @@ public function reemit(Invoice $invoice): RedirectResponse $sandboxReemit = (bool) setting('nfse.sandbox_mode', true); $tomadorDocument = $this->normalizedTomadorDocument($invoice->contact?->tax_number); $opcaoSimplesNacional = $this->normalizedOpcaoSimplesNacional(); + $federalPayload = $this->federalPayloadValues((float) $invoice->amount); $dps = new DpsData( cnpjPrestador: setting('nfse.cnpj_prestador'), municipioIbge: setting('nfse.municipio_ibge'), - itemListaServico: setting('nfse.item_lista_servico', '0107'), - codigoTributacaoNacional: $this->nationalTaxCode(), + itemListaServico: $this->itemListaServico($defaultService), + codigoTributacaoNacional: $this->nationalTaxCode($defaultService), valorServico: number_format((float) $invoice->amount, 2, '.', ''), - aliquota: $this->normalizedAliquota(), + aliquota: $this->normalizedAliquota($defaultService), discriminacao: $this->buildDiscriminacao($invoice), documentoTomador: $tomadorDocument, nomeTomador: $invoice->contact?->name ?? '', opcaoSimplesNacional: $opcaoSimplesNacional, tipoAmbiente: $sandboxReemit ? 2 : 1, + serie: $this->dpsSerie($invoice), + numeroDps: $this->dpsNumber($invoice), + dataCompetencia: $this->competenceDate($invoice), + indicadorTributacao: $federalPayload['indicadorTributacao'], + federalPiscofinsSituacaoTributaria: $federalPayload['federalPiscofinsSituacaoTributaria'], + federalPiscofinsTipoRetencao: $federalPayload['federalPiscofinsTipoRetencao'], + federalPiscofinsBaseCalculo: $federalPayload['federalPiscofinsBaseCalculo'], + federalPiscofinsAliquotaPis: $federalPayload['federalPiscofinsAliquotaPis'], + federalPiscofinsValorPis: $federalPayload['federalPiscofinsValorPis'], + federalPiscofinsAliquotaCofins: $federalPayload['federalPiscofinsAliquotaCofins'], + federalPiscofinsValorCofins: $federalPayload['federalPiscofinsValorCofins'], + federalValorIrrf: $federalPayload['federalValorIrrf'], + federalValorCsll: $federalPayload['federalValorCsll'], + federalValorCp: $federalPayload['federalValorCp'], ); $client = $this->makeClient($sandboxReemit); @@ -503,6 +536,7 @@ protected function emissionReadiness(): array { $settings = setting('nfse', []); $settings = is_array($settings) ? $settings : []; + $defaultService = $this->resolveDefaultCompanyService(); $cnpj = (string) ($settings['cnpj_prestador'] ?? ''); $certificatePath = $cnpj !== '' ? storage_path('app/nfse/pfx/' . $cnpj . '.pfx') : ''; $transportCertificatePath = $this->projectRootPath('client.crt.pem'); @@ -511,7 +545,7 @@ protected function emissionReadiness(): array $checklist = [ 'cnpj_prestador' => $cnpj !== '', 'municipio_ibge' => ((string) ($settings['municipio_ibge'] ?? '')) !== '', - 'item_lista_servico' => ((string) ($settings['item_lista_servico'] ?? '')) !== '', + 'item_lista_servico' => $this->itemListaServico($defaultService) !== '', 'certificate' => $certificatePath !== '' && is_file($certificatePath), 'certificate_secret' => $this->hasCertificateSecret($cnpj), 'transport_certificate' => is_file($transportCertificatePath) && is_file($transportPrivateKeyPath), @@ -523,27 +557,127 @@ protected function emissionReadiness(): array ]; } - protected function nationalTaxCode(): string + protected function itemListaServico(?object $defaultService = null): string { - $configured = preg_replace('/\D+/', '', (string) setting('nfse.codigo_tributacao_nacional', '')) ?: ''; + $serviceCode = preg_replace('/\D+/', '', (string) ($defaultService->item_lista_servico ?? '')) ?: ''; + + if ($serviceCode !== '') { + return substr($serviceCode, 0, 4); + } + + return preg_replace('/\D+/', '', (string) setting('nfse.item_lista_servico', '0107')) ?: '0107'; + } + + protected function nationalTaxCode(?object $defaultService = null): string + { + $configured = preg_replace('/\D+/', '', (string) ($defaultService->codigo_tributacao_nacional ?? '')) ?: ''; + + if ($configured === '') { + $configured = preg_replace('/\D+/', '', (string) setting('nfse.codigo_tributacao_nacional', '')) ?: ''; + } if ($configured !== '') { return str_pad(substr($configured, 0, 6), 6, '0', STR_PAD_LEFT); } - $lc116Code = preg_replace('/\D+/', '', (string) setting('nfse.item_lista_servico', '0107')) ?: '0107'; - - return str_pad(substr($lc116Code, 0, 4), 4, '0', STR_PAD_LEFT) . '01'; + return ''; } - protected function normalizedAliquota(): string + protected function normalizedAliquota(?object $defaultService = null): string { - $configured = (string) setting('nfse.aliquota', '5.00'); + $configured = (string) ($defaultService->aliquota ?? ''); + + if ($configured === '') { + $configured = (string) setting('nfse.aliquota', '5.00'); + } + $normalized = str_replace(',', '.', trim($configured)); return number_format((float) $normalized, 2, '.', ''); } + protected function dpsSerie(Invoice $invoice): string + { + return '00001'; + } + + protected function dpsNumber(Invoice $invoice): string + { + $invoiceId = isset($invoice->id) ? (int) $invoice->id : 0; + + return (string) max($invoiceId, 1); + } + + protected function competenceDate(Invoice $invoice): ?string + { + $issuedAt = $invoice->issued_at ?? null; + + if ($issuedAt instanceof \DateTimeInterface) { + return $issuedAt->format('Y-m-d'); + } + + if (is_string($issuedAt) && $issuedAt !== '') { + $timestamp = strtotime($issuedAt); + + if ($timestamp !== false) { + return date('Y-m-d', $timestamp); + } + } + + return null; + } + + protected function resolveDefaultCompanyService(?Invoice $invoice = null): ?object + { + if (! $this->supportsCompanyServiceSelection()) { + return null; + } + + $companyId = is_numeric($invoice?->company_id ?? null) ? (int) $invoice->company_id : $this->resolveCompanyId(); + + if ($companyId <= 0) { + return null; + } + + return CompanyService::where('company_id', $companyId) + ->where('is_default', true) + ->where('is_active', true) + ->first(); + } + + protected function supportsCompanyServiceSelection(): bool + { + if (! class_exists(\Illuminate\Database\Eloquent\Model::class)) { + return false; + } + + return class_exists(CompanyService::class) + && is_subclass_of(CompanyService::class, \Illuminate\Database\Eloquent\Model::class); + } + + protected function resolveCompanyId(): int + { + if (function_exists('company_id')) { + $companyId = (int) (company_id() ?? 0); + + if ($companyId > 0) { + return $companyId; + } + } + + if (! function_exists('auth')) { + return 0; + } + + try { + $user = auth()->user(); + } catch (\Throwable) { + return 0; + } + + return $user !== null && isset($user->company_id) ? (int) $user->company_id : 0; + } + protected function normalizedOpcaoSimplesNacional(): int { $configured = (int) setting('nfse.opcao_simples_nacional', 2); @@ -551,6 +685,101 @@ protected function normalizedOpcaoSimplesNacional(): int return in_array($configured, [1, 2], true) ? $configured : 2; } + protected function federalPayloadValues(float $invoiceAmount): array + { + $mode = (string) setting('nfse.tributacao_federal_mode', 'per_invoice_amounts'); + $situacaoTributaria = $this->normalizedFederalSelectValue(setting('nfse.federal_piscofins_situacao_tributaria', '')); + $tipoRetencao = $this->normalizedFederalSelectValue(setting('nfse.federal_piscofins_tipo_retencao', '')); + + $indicadorTributacao = ( + setting('nfse.tributos_fed_p', '') !== '' || + setting('nfse.tributos_est_p', '') !== '' || + setting('nfse.tributos_mun_p', '') !== '' || + setting('nfse.tributos_fed_sn', '') !== '' || + setting('nfse.tributos_est_sn', '') !== '' || + setting('nfse.tributos_mun_sn', '') !== '' + ) ? 2 : 0; + + if ($situacaoTributaria === '' || $situacaoTributaria === '0') { + return [ + 'federalPiscofinsSituacaoTributaria' => '', + 'federalPiscofinsTipoRetencao' => '', + 'federalPiscofinsBaseCalculo' => '', + 'federalPiscofinsAliquotaPis' => '', + 'federalPiscofinsValorPis' => '', + 'federalPiscofinsAliquotaCofins' => '', + 'federalPiscofinsValorCofins' => '', + 'federalValorIrrf' => $this->normalizedFederalDecimal(setting('nfse.federal_valor_irrf', '')), + 'federalValorCsll' => '', + 'federalValorCp' => $this->normalizedFederalDecimal(setting('nfse.federal_valor_cp', '')), + 'indicadorTributacao' => $indicadorTributacao, + ]; + } + + $valorCsll = ($tipoRetencao !== '' && $tipoRetencao !== '0') + ? $this->normalizedFederalDecimal(setting('nfse.federal_valor_csll', '')) + : ''; + + if ($mode === 'percentage_profile') { + $aliquotaPis = $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_aliquota_pis', '')); + $aliquotaCofins = $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_aliquota_cofins', '')); + + return [ + 'federalPiscofinsSituacaoTributaria' => $situacaoTributaria, + 'federalPiscofinsTipoRetencao' => $tipoRetencao, + 'federalPiscofinsBaseCalculo' => number_format($invoiceAmount, 2, '.', ''), + 'federalPiscofinsAliquotaPis' => $aliquotaPis, + 'federalPiscofinsValorPis' => $aliquotaPis !== '' + ? number_format($invoiceAmount * (float) $aliquotaPis / 100, 2, '.', '') + : '', + 'federalPiscofinsAliquotaCofins' => $aliquotaCofins, + 'federalPiscofinsValorCofins' => $aliquotaCofins !== '' + ? number_format($invoiceAmount * (float) $aliquotaCofins / 100, 2, '.', '') + : '', + 'federalValorIrrf' => $this->normalizedFederalDecimal(setting('nfse.federal_valor_irrf', '')), + 'federalValorCsll' => $valorCsll, + 'federalValorCp' => $this->normalizedFederalDecimal(setting('nfse.federal_valor_cp', '')), + 'indicadorTributacao' => $indicadorTributacao, + ]; + } + + return [ + 'federalPiscofinsSituacaoTributaria' => $situacaoTributaria, + 'federalPiscofinsTipoRetencao' => $tipoRetencao, + 'federalPiscofinsBaseCalculo' => $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_base_calculo', '')), + 'federalPiscofinsAliquotaPis' => $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_aliquota_pis', '')), + 'federalPiscofinsValorPis' => $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_valor_pis', '')), + 'federalPiscofinsAliquotaCofins' => $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_aliquota_cofins', '')), + 'federalPiscofinsValorCofins' => $this->normalizedFederalDecimal(setting('nfse.federal_piscofins_valor_cofins', '')), + 'federalValorIrrf' => $this->normalizedFederalDecimal(setting('nfse.federal_valor_irrf', '')), + 'federalValorCsll' => $valorCsll, + 'federalValorCp' => $this->normalizedFederalDecimal(setting('nfse.federal_valor_cp', '')), + 'indicadorTributacao' => $indicadorTributacao, + ]; + } + + protected function normalizedFederalSelectValue(mixed $value): string + { + $normalized = trim((string) $value); + + return preg_match('/^\d+$/', $normalized) === 1 ? $normalized : ''; + } + + protected function normalizedFederalDecimal(mixed $value): string + { + if (!is_string($value) && !is_numeric($value)) { + return ''; + } + + $normalized = str_replace(',', '.', trim((string) $value)); + + if ($normalized === '' || !is_numeric($normalized)) { + return ''; + } + + return number_format((float) $normalized, 2, '.', ''); + } + protected function hasCertificateSecret(string $cnpj): bool { if ($cnpj === '') { diff --git a/Http/Controllers/SettingsController.php b/Http/Controllers/SettingsController.php index 92ed6ec3..a420a874 100644 --- a/Http/Controllers/SettingsController.php +++ b/Http/Controllers/SettingsController.php @@ -40,7 +40,7 @@ public function edit(?Request $request = null): \Illuminate\View\View $vaultUiState = $this->vaultUiState($settingsArray, $certificateState); $rawTab = $request !== null ? $request->query('tab') : null; - $activeTab = (is_string($rawTab) && in_array($rawTab, ['vault', 'certificate', 'fiscal', 'services'], true)) + $activeTab = (is_string($rawTab) && in_array($rawTab, ['vault', 'certificate', 'fiscal', 'federal', 'services'], true)) ? $rawTab : 'vault'; @@ -77,6 +77,7 @@ public function edit(?Request $request = null): \Illuminate\View\View } $companyServices = []; + $defaultCompanyService = null; if ($companyId > 0 && method_exists(CompanyService::class, 'query')) { $query = CompanyService::where('company_id', $companyId); @@ -102,6 +103,13 @@ public function edit(?Request $request = null): \Illuminate\View\View ->orderBy('is_default', 'desc') ->orderBy('created_at') ->get(); + + foreach ($companyServices as $companyService) { + if (($companyService->is_default ?? false) && ($companyService->is_active ?? true)) { + $defaultCompanyService = $companyService; + break; + } + } } return view('nfse::settings.edit', [ @@ -110,6 +118,7 @@ public function edit(?Request $request = null): \Illuminate\View\View 'vaultUiState' => $vaultUiState, 'activeTab' => $activeTab, 'companyServices' => $companyServices, + 'defaultCompanyService' => $defaultCompanyService, 'servicesSearch' => $servicesSearch, 'servicesStatus' => $servicesStatus, ]); @@ -158,6 +167,7 @@ public function updateFiscal(Request $request): RedirectResponse 'nfse.uf' => 'required|string|size:2', 'nfse.municipio_nome' => 'required|string|max:255', 'nfse.municipio_ibge' => 'required|string|size:7', + 'nfse.opcao_simples_nacional' => 'nullable|in:1,2', 'nfse.sandbox_mode' => 'nullable|boolean', ]); @@ -167,7 +177,7 @@ public function updateFiscal(Request $request): RedirectResponse $fiscalInput = $rawNfseInput; $fiscalInput['uf'] = strtoupper((string) ($fiscalInput['uf'] ?? '')); - foreach (['cnpj_prestador', 'uf', 'municipio_nome', 'municipio_ibge', 'sandbox_mode'] as $key) { + foreach (['cnpj_prestador', 'uf', 'municipio_nome', 'municipio_ibge', 'opcao_simples_nacional', 'sandbox_mode'] as $key) { if (array_key_exists($key, $fiscalInput)) { setting(['nfse.' . $key => $fiscalInput[$key]]); } @@ -179,6 +189,88 @@ public function updateFiscal(Request $request): RedirectResponse ->with('success', trans('nfse::general.saved')); } + public function updateFederal(Request $request): RedirectResponse + { + $settings = setting('nfse', []); + $settingsArray = is_array($settings) ? $settings : []; + + if (!$this->isVaultReady($settingsArray)) { + return redirect()->route('nfse.settings.edit', ['tab' => 'vault']) + ->with('error', trans('nfse::general.vault_required_before_certificate_and_settings')); + } + + $request->validate([ + 'nfse.tributacao_federal_mode' => 'required|in:per_invoice_amounts,percentage_profile', + 'nfse.federal_piscofins_situacao_tributaria' => 'nullable|regex:/^\d+$/', + 'nfse.federal_piscofins_tipo_retencao' => 'nullable|regex:/^\d+$/', + 'nfse.federal_piscofins_base_calculo' => 'nullable|numeric|min:0', + 'nfse.federal_piscofins_aliquota_pis' => 'nullable|numeric|min:0|max:100', + 'nfse.federal_piscofins_valor_pis' => 'nullable|numeric|min:0', + 'nfse.federal_piscofins_aliquota_cofins' => 'nullable|numeric|min:0|max:100', + 'nfse.federal_piscofins_valor_cofins' => 'nullable|numeric|min:0', + 'nfse.federal_valor_irrf' => 'nullable|numeric|min:0', + 'nfse.federal_valor_csll' => 'nullable|numeric|min:0', + 'nfse.federal_valor_cp' => 'nullable|numeric|min:0', + 'nfse.tributos_fed_p' => 'nullable|numeric|min:0|max:100', + 'nfse.tributos_est_p' => 'nullable|numeric|min:0|max:100', + 'nfse.tributos_mun_p' => 'nullable|numeric|min:0|max:100', + 'nfse.tributos_fed_sn' => 'nullable|numeric|min:0|max:100', + 'nfse.tributos_est_sn' => 'nullable|numeric|min:0|max:100', + 'nfse.tributos_mun_sn' => 'nullable|numeric|min:0|max:100', + ]); + + $rawNfseInput = $request->input('nfse', []); + $rawNfseInput = is_array($rawNfseInput) ? $rawNfseInput : []; + + $keys = [ + 'tributacao_federal_mode', + 'federal_piscofins_situacao_tributaria', + 'federal_piscofins_tipo_retencao', + 'federal_piscofins_base_calculo', + 'federal_piscofins_aliquota_pis', + 'federal_piscofins_valor_pis', + 'federal_piscofins_aliquota_cofins', + 'federal_piscofins_valor_cofins', + 'federal_valor_irrf', + 'federal_valor_csll', + 'federal_valor_cp', + 'tributos_fed_p', + 'tributos_est_p', + 'tributos_mun_p', + 'tributos_fed_sn', + 'tributos_est_sn', + 'tributos_mun_sn', + ]; + + foreach ($keys as $key) { + if (!array_key_exists($key, $rawNfseInput)) { + continue; + } + + $value = $rawNfseInput[$key]; + + if (in_array($key, ['federal_piscofins_situacao_tributaria', 'federal_piscofins_tipo_retencao'], true)) { + $value = trim((string) $value); + $value = preg_match('/^\d+$/', $value) === 1 ? $value : null; + } elseif ($key !== 'tributacao_federal_mode') { + $value = is_string($value) ? str_replace(',', '.', trim($value)) : $value; + + if ($value === '' || $value === null) { + $value = null; + } elseif (is_numeric($value)) { + $value = number_format((float) $value, 2, '.', ''); + } + } + + setting(['nfse.' . $key => $value]); + } + + setting()->save(); + + return redirect()->route('nfse.settings.edit', ['tab' => 'federal']) + ->with('success', trans('nfse::general.saved')); + } + public function ufs(IbgeLocalities $ibgeLocalities): JsonResponse { try { @@ -289,8 +381,6 @@ public function update(Request $request): RedirectResponse 'nfse.uf' => 'required|string|size:2', 'nfse.municipio_nome' => 'required|string|max:255', 'nfse.municipio_ibge' => 'required|string|size:7', - 'nfse.item_lista_servico' => 'required|string|size:4', - 'nfse.codigo_tributacao_nacional' => 'nullable|string|size:6', 'nfse.sandbox_mode' => 'nullable|boolean', 'nfse.bao_addr' => 'required|url', 'nfse.bao_mount' => 'required|string', diff --git a/Resources/lang/en-GB/general.php b/Resources/lang/en-GB/general.php index d9f7ad48..c3e1930d 100644 --- a/Resources/lang/en-GB/general.php +++ b/Resources/lang/en-GB/general.php @@ -52,18 +52,19 @@ 'go_to_dashboard' => 'Go to NFS-e dashboard', 'go_to_invoices' => 'Go to issued invoices', 'go_to_pending_invoices' => 'Go to pending invoices', - 'go_to_readiness' => 'View operational readiness', - 'go_to_settings' => 'Go to settings', + 'go_to_readiness' => 'View settings', + 'go_to_settings' => 'View settings', 'readiness' => [ - 'title' => 'NFS-e operational readiness', - 'ready' => 'Environment ready for issuance', - 'not_ready' => 'Environment is not ready yet', - 'hint' => 'Complete the pending checks below to reduce issuance failures.', + 'title' => 'NFS-e settings', + 'ready' => 'Settings ready for issuance', + 'not_ready' => 'Pending settings', + 'hint' => 'Complete the pending items below to enable issuance.', 'checks' => [ 'cnpj_prestador' => 'Service provider CNPJ saved', 'municipio_ibge' => 'IBGE municipality configured', 'item_lista_servico' => 'LC 116 service item configured', + 'codigo_tributacao_nacional' => 'National tax code (NBS) configured', 'bao_addr' => 'OpenBao address configured', 'bao_mount' => 'OpenBao mount configured', 'certificate' => 'Local certificate available', @@ -97,8 +98,8 @@ 'issue_date' => 'Issue date', 'back_to_list' => 'Back to list', 'emit_now' => 'Emit NFS-e now', - 'emit_blocked_not_ready' => 'Environment is not ready for issuance.', - 'readiness_incomplete' => 'There are pending configuration checks. Resolve operational readiness before issuing.', + 'emit_blocked_not_ready' => 'There are pending settings before issuance can continue.', + 'readiness_incomplete' => 'There are pending configuration items. Review settings before issuing.', 'no_pending' => 'No pending invoices to emit.', 'cancel' => 'Cancel NFS-e', 'refresh_status' => 'Refresh status', @@ -132,6 +133,9 @@ 'uf' => 'State (UF)', 'municipio_nome' => 'Municipality', 'municipio_ibge' => 'Municipality IBGE Code', + 'opcao_simples_nacional' => 'Simples Nacional status', + 'opcao_simples_nacional_not_optant' => 'Not opted into Simples Nacional', + 'opcao_simples_nacional_optant' => 'Opted into Simples Nacional', 'sandbox_mode' => 'Sandbox Mode (Staging)', 'vault_section_title' => '1. Vault server', 'bao_addr' => 'Vault server address', @@ -174,9 +178,39 @@ 'codigo_tributacao_nacional' => 'National tax code (NBS)', 'codigo_tributacao_nacional_hint' => 'Enter the 6-digit national service code. Example: 010701 for technical IT support.', 'aliquota' => 'ISS Rate (%)', - 'services_config_moved_hint' => 'Service settings (LC116 item, national code and rate) are now managed exclusively in tab 4. Services.', + 'services_config_moved_hint' => 'Service settings (LC116 item, national code and rate) are now managed exclusively in tab 5. Services.', + 'current_default_service' => 'Current default service: :service', + 'default_service_missing' => 'No active default service is configured. Choose a default service in tab 5. Services before issuing NFS-e.', + 'manage_default_service' => 'Manage default service', + 'federal' => [ + 'tab_title' => '4. Federal Taxation', + 'heading' => 'Federal Taxation', + 'helper' => 'Configure behavior and approximate federal, state and municipal tax percentages used for NFS-e issuance.', + 'current_simples_status' => 'Current Simples Nacional status', + 'behavior_label' => 'Fill behavior', + 'behavior_hint' => 'The selected option is applied as the default for NFS-e issuances.', + 'mode_per_invoice_amounts' => 'Fill monetary values for each issued NFS-e', + 'mode_percentage_profile' => 'Configure the corresponding percentage values', + 'select_placeholder' => 'Select...', + 'piscofins_situacao_tributaria' => 'PIS/COFINS Tax Situation', + 'piscofins_tipo_retencao' => 'PIS/COFINS/CSLL retention type', + 'piscofins_base_calculo' => 'PIS/COFINS Tax Base', + 'piscofins_aliquota_pis' => 'PIS - Rate', + 'piscofins_valor_pis' => 'PIS - Own Assessment Debit', + 'piscofins_aliquota_cofins' => 'COFINS - Rate', + 'piscofins_valor_cofins' => 'COFINS - Own Assessment Debit', + 'valor_irrf' => 'IRRF', + 'valor_csll' => 'Social Contributions - Withheld', + 'valor_cp' => 'Social Security Contribution - Withheld', + 'tributos_fed_p' => 'Federal taxes (%) - Default profile', + 'tributos_est_p' => 'State taxes (%) - Default profile', + 'tributos_mun_p' => 'Municipal taxes (%) - Default profile', + 'tributos_fed_sn' => 'Federal taxes (%) - Simples Nacional', + 'tributos_est_sn' => 'State taxes (%) - Simples Nacional', + 'tributos_mun_sn' => 'Municipal taxes (%) - Simples Nacional', + ], 'services' => [ - 'tab_title' => '4. Services', + 'tab_title' => '5. Services', 'title' => 'Service Manager', 'description' => 'Configure multiple services (LC 116) and rates for your company. Each service can be used when issuing NFS-e.', 'lc116_code' => 'LC 116 Code', diff --git a/Resources/lang/pt-BR/general.php b/Resources/lang/pt-BR/general.php index d0ca4dea..24395f89 100644 --- a/Resources/lang/pt-BR/general.php +++ b/Resources/lang/pt-BR/general.php @@ -52,18 +52,19 @@ 'go_to_dashboard' => 'Ir para o painel NFS-e', 'go_to_invoices' => 'Ir para notas emitidas', 'go_to_pending_invoices' => 'Ir para faturas pendentes', - 'go_to_readiness' => 'Ver prontidão operacional', - 'go_to_settings' => 'Ir para configurações', + 'go_to_readiness' => 'Ver configurações', + 'go_to_settings' => 'Ver configurações', 'readiness' => [ - 'title' => 'Prontidão operacional NFS-e', - 'ready' => 'Ambiente pronto para emissão', - 'not_ready' => 'Ambiente ainda não está pronto', - 'hint' => 'Complete os itens pendentes abaixo para reduzir falhas de emissão.', + 'title' => 'Configurações NFS-e', + 'ready' => 'Configurações prontas para emissão', + 'not_ready' => 'Configurações pendentes', + 'hint' => 'Complete os itens pendentes abaixo para liberar a emissão.', 'checks' => [ 'cnpj_prestador' => 'CNPJ do prestador salvo', 'municipio_ibge' => 'Município IBGE configurado', 'item_lista_servico' => 'Item da lista LC 116 configurado', + 'codigo_tributacao_nacional' => 'Código de tributação nacional (NBS) configurado', 'bao_addr' => 'Endereço OpenBao configurado', 'bao_mount' => 'Mount OpenBao configurado', 'certificate' => 'Certificado local disponível', @@ -97,8 +98,8 @@ 'issue_date' => 'Data de emissão', 'back_to_list' => 'Voltar para lista', 'emit_now' => 'Emitir NFS-e agora', - 'emit_blocked_not_ready' => 'Ambiente não está pronto para emissão.', - 'readiness_incomplete' => 'Existem pendências de configuração. Resolva a prontidão operacional antes de emitir.', + 'emit_blocked_not_ready' => 'Existem configurações pendentes para liberar a emissão.', + 'readiness_incomplete' => 'Existem pendências de configuração. Revise as configurações antes de emitir.', 'no_pending' => 'Nenhuma fatura pendente de emissão.', 'cancel' => 'Cancelar NFS-e', 'refresh_status' => 'Atualizar status', @@ -132,6 +133,9 @@ 'uf' => 'Estado (UF)', 'municipio_nome' => 'Município', 'municipio_ibge' => 'Código IBGE do Município', + 'opcao_simples_nacional' => 'Situação no Simples Nacional', + 'opcao_simples_nacional_not_optant' => 'Não optante', + 'opcao_simples_nacional_optant' => 'Optante', 'sandbox_mode' => 'Modo Sandbox (Homologação)', 'vault_section_title' => '1. Servidor Vault', 'bao_addr' => 'Endereço do servidor Vault', @@ -174,9 +178,39 @@ 'codigo_tributacao_nacional' => 'Código de tributação nacional (NBS)', 'codigo_tributacao_nacional_hint' => 'Opcional. Informe o código nacional de 6 dígitos apenas quando necessário para o seu cenário fiscal.', 'aliquota' => 'Alíquota ISS (%)', - 'services_config_moved_hint' => 'Configurações de serviço (item LC116, código nacional e alíquota) agora são gerenciadas exclusivamente na aba 4. Serviços.', + 'services_config_moved_hint' => 'Configurações de serviço (item LC116, código nacional e alíquota) agora são gerenciadas exclusivamente na aba 5. Serviços.', + 'current_default_service' => 'Serviço padrão atual: :service', + 'default_service_missing' => 'Nenhum serviço padrão ativo foi definido. Escolha um serviço padrão na aba 5. Serviços antes de emitir NFS-e.', + 'manage_default_service' => 'Gerenciar serviço padrão', + 'federal' => [ + 'tab_title' => '4. Tributação Federal', + 'heading' => 'Tributação Federal', + 'helper' => 'Configure o comportamento e os percentuais aproximados de tributos federais, estaduais e municipais para emissão da NFS-e.', + 'current_simples_status' => 'Opção atual no Simples Nacional', + 'behavior_label' => 'Comportamento de preenchimento', + 'behavior_hint' => 'A opção selecionada será aplicada como padrão nas emissões de NFS-e.', + 'mode_per_invoice_amounts' => 'Preencher os valores monetários em cada NFS-e emitida', + 'mode_percentage_profile' => 'Configurar os valores percentuais correspondentes', + 'select_placeholder' => 'Selecione...', + 'piscofins_situacao_tributaria' => 'Situação Tributária do PIS/COFINS', + 'piscofins_tipo_retencao' => 'Tipo de retenção do PIS/COFINS/CSLL', + 'piscofins_base_calculo' => 'BC PIS/COFINS', + 'piscofins_aliquota_pis' => 'PIS - Alíquota', + 'piscofins_valor_pis' => 'PIS - Débito Apuração Própria', + 'piscofins_aliquota_cofins' => 'COFINS - Alíquota', + 'piscofins_valor_cofins' => 'COFINS - Débito Apuração Própria', + 'valor_irrf' => 'IRRF', + 'valor_csll' => 'Contribuições Sociais - Retidas', + 'valor_cp' => 'Contribuição Previdenciária - Retida', + 'tributos_fed_p' => 'Tributos federais (%) - Perfil padrão', + 'tributos_est_p' => 'Tributos estaduais (%) - Perfil padrão', + 'tributos_mun_p' => 'Tributos municipais (%) - Perfil padrão', + 'tributos_fed_sn' => 'Tributos federais (%) - Simples Nacional', + 'tributos_est_sn' => 'Tributos estaduais (%) - Simples Nacional', + 'tributos_mun_sn' => 'Tributos municipais (%) - Simples Nacional', + ], 'services' => [ - 'tab_title' => '4. Serviços', + 'tab_title' => '5. Serviços', 'title' => 'Gerenciador de Serviços', 'description' => 'Configure múltiplos serviços (LC 116) e alíquotas para sua empresa. Cada serviço pode ser usado ao emitir uma NFS-e.', 'lc116_code' => 'Código LC 116', diff --git a/Resources/views/invoices/pending.blade.php b/Resources/views/invoices/pending.blade.php index 03d3bdf0..10ca70c1 100644 --- a/Resources/views/invoices/pending.blade.php +++ b/Resources/views/invoices/pending.blade.php @@ -35,8 +35,8 @@ @endforeach @endif - - {{ trans('nfse::general.go_to_readiness') }} + + {{ trans('nfse::general.go_to_settings') }} @endif diff --git a/Resources/views/settings/edit.blade.php b/Resources/views/settings/edit.blade.php index 4c023c11..dcee2e03 100644 --- a/Resources/views/settings/edit.blade.php +++ b/Resources/views/settings/edit.blade.php @@ -30,8 +30,17 @@ 'vault' => ['label' => trans('nfse::general.settings.vault_section_title'), 'enabled' => true], 'certificate' => ['label' => trans('nfse::general.step_certificate'), 'enabled' => $vaultReady], 'fiscal' => ['label' => trans('nfse::general.step_settings'), 'enabled' => $hasSavedSettings], + 'federal' => ['label' => trans('nfse::general.settings.federal.tab_title'), 'enabled' => $hasSavedSettings], 'services' => ['label' => trans('nfse::general.settings.services.tab_title'), 'enabled' => $hasSavedSettings], ]; + + $selectedFederalMode = old('nfse.tributacao_federal_mode', setting('nfse.tributacao_federal_mode', 'per_invoice_amounts')); + if (! in_array($selectedFederalMode, ['per_invoice_amounts', 'percentage_profile'], true)) { + $selectedFederalMode = 'per_invoice_amounts'; + } + $simplesStatus = (string) old('nfse.opcao_simples_nacional', setting('nfse.opcao_simples_nacional', 2)) === '1' + ? trans('nfse::general.settings.opcao_simples_nacional_not_optant') + : trans('nfse::general.settings.opcao_simples_nacional_optant'); @endphp {{-- ── Tab navigation ──────────────────────────────────────── --}} @@ -345,10 +354,11 @@ class="absolute inset-y-0 right-0 px-3 text-gray-500 hover:text-gray-700"
- - - - + +