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"
-
-
-
-
+
+