boundRule()->where('status', 'published')->exists(); return ['enabled' => $eligible, 'coverage_start' => '2026-01-01', 'coverage_end' => '2026-12-31', 'message' => $eligible ? 'IVA geral sobre base líquida disponível; enquadramento declarado.' : 'O cálculo de IVA geral sobre base líquida está indisponível para novas execuções. Os históricos mantêm-se acessíveis.']; } private function boundRule(): Builder { if (! Str::isUuid((string) config('simulation-fiscal-consumer.rule_id')) || config('simulation-fiscal-consumer.rule_version') < 1) { return SimulationTaxRuleVersion::query()->whereRaw('false'); } return SimulationTaxRuleVersion::query()->whereKey(config('simulation-fiscal-consumer.rule_id')) ->where('rule_version', config('simulation-fiscal-consumer.rule_version')) ->where('rule_code', 'IVA-GERAL-BASE-LIQUIDA')->where('component', 'IVA') ->where('scope_key', 'mz.internal-general-net.mzn'); } public function admit(array $input, bool $missingBase = false): array { if (isset($input['monthly_sales'])) { return $this->admitMonthly($input); } // Called inside createPayload, after idempotent replay, never by the worker. abort_unless(DB::transactionLevel() > 0, 500); abort_unless(config('simulation-fiscal-consumer.enabled'), 503, 'O cálculo de IVA está temporariamente indisponível. Os dados e históricos foram preservados.'); DB::select('select pg_advisory_xact_lock_shared(261021, hashtext(?))', [(string) config('simulation-fiscal-consumer.rule_id')]); $rule = $this->boundRule()->first(); abort_unless($rule && $rule->status === 'published', 503, 'A versão autorizada de IVA não está disponível para novas execuções. Os históricos foram preservados.'); abort_if($input['reference_date'] < '2026-01-01' || $input['reference_date'] > '2026-12-31', 422, 'Este cálculo abrange apenas exigibilidade em 2026. As entradas foram preservadas.'); abort_if(collect($rule->rule_definition['nodes'] ?? [])->contains(fn (array $node): bool => ($node['op'] ?? null) === 'round'), 422, 'A versão seleccionada não é compatível com o cálculo exacto deste consumidor.'); DB::select('select pg_advisory_xact_lock_shared(261022, hashtext(?))', [(string) $rule->legal_source_id]); $source = InvestmentLegalSource::whereKey($rule->legal_source_id)->firstOrFail(); // Publish calculation provenance, not private governance notes or staff identifiers. $version = array_intersect_key($rule->attributesToArray(), array_flip([ 'id', 'rule_code', 'rule_version', 'component', 'scope_key', 'status', 'valid_from', 'valid_until', 'legal_source_id', 'source_version_no', 'source_sha256', 'rule_definition', 'parameters', 'rounding_policy', ])); $version['source'] = array_intersect_key($source->attributesToArray(), array_flip([ 'id', 'title', 'url', 'diploma', 'article', 'competent_entity', 'valid_from', 'valid_until', 'validation_status', 'version_no', ])); $resolution = app(SimulationTaxParameterResolver::class)->resolvePublishedForPeriod('IVA', $input['reference_date'], $input['reference_date'], ['currency' => $input['currency'], 'operation' => $input['operation']]); $rate = collect($resolution)->firstWhere('parameter_code', 'IVA_GENERAL_RATE'); $missing = []; if ($missingBase) { $missing[] = 'taxable_sales'; } $evaluation = null; if (! ($input['scope_declared'] ?? false)) { $missing[] = 'operation_scope'; } if (($rate['status'] ?? null) !== 'resolved' || ($rate['unit'] ?? null) !== 'percent' || ($rate['rule_id'] ?? null) !== (string) $rule->id || (int) ($rate['rule_version'] ?? 0) !== (int) $rule->rule_version) { $missing[] = 'eligible_parameter'; } if ($missing === []) { $evaluation = app(SimulationTaxRuleExpression::class)->evaluate($rule->rule_definition, ['base_tributavel' => $input['base']], ['iva_general_rate' => $rate['value']]); } return app(SimulationFiscalSnapshot::class)->build($version, $input, $resolution, $evaluation, $missing, true); } private function admitMonthly(array $input): array { $rows = []; $baseTotal = BigRational::zero(); $vatTotal = BigRational::zero(); $complete = true; $previous = null; foreach ($input['monthly_sales'] as $sale) { abort_unless($previous === null || $sale['date'] === date('Y-m-d', strtotime($previous.' +1 month')), 422, 'A projecção deve conter meses consecutivos, sem duplicações.'); $previous = $sale['date']; $known = $sale['amount'] !== null && $sale['amount'] !== ''; $net = $input['sales_vat_basis'] === 'net'; $month = $this->admit(['base' => $known && $net ? $sale['amount'] : null, 'currency' => $input['currency'], 'reference_date' => $sale['date'], 'operation' => 'internal_general_net', 'scope_declared' => $input['scope_declared']], ! $known || ! $net); $snapshot ??= $month; $rate = collect($month['fiscal_provenance']['resolution'])->firstWhere('parameter_code', 'IVA_GENERAL_RATE'); $vat = $month['result']['value']; $status = ! $known ? 'missing_sales' : (! $net ? 'unsupported_basis' : ($vat === null ? 'indeterminate' : 'calculated')); $rows[] = ['date' => $sale['date'], 'sales_declared' => $sale['amount'], 'net_sales' => $known && $net ? $sale['amount'] : null, 'rate' => $vat !== null && ($rate['status'] ?? null) === 'resolved' ? $rate['value'] : null, 'vat' => $vat, 'gross_sales' => $vat === null ? null : (string) BigRational::of($sale['amount'])->plus($vat)->toBigDecimal(), 'status' => $status, 'rule_id' => $month['fiscal_provenance']['rule']['id'], 'rule_version' => $month['fiscal_provenance']['rule']['rule_version']]; if ($vat === null) { $complete = false; } else { $baseTotal = $baseTotal->plus($sale['amount']); $vatTotal = $vatTotal->plus($vat); } } $value = $complete ? (string) $vatTotal->toBigDecimal() : null; $snapshot['base'] = $complete ? (string) $baseTotal->toBigDecimal() : null; $snapshot['indicative_total'] = $value; $snapshot['result']['value'] = $value; $snapshot['result']['status'] = $complete ? 'calculated' : 'indeterminate'; $snapshot['result']['missing_inputs'] = $complete ? [] : ['monthly_sales_or_net_basis_or_scope']; $snapshot['result']['explanation'] = 'IVA mensal sobre vendas declaradas sem IVA, pela versão publicada congelada na admissão. Valores com IVA incluído ou sem informação permanecem indeterminados. Não é imposto final a pagar.'; $snapshot['presentation']['value'] = $value; $snapshot['intermediate'] = null; $snapshot['monthly_projection'] = ['input' => $input, 'rows' => $rows, 'cash_treatment' => 'Informação separada da tesouraria: os recebimentos declarados não são aumentados pelo IVA nem o IVA é deduzido como custo ou imposto a pagar. Calendário de cobrança e liquidação fiscal não é inferido.']; return $snapshot; } }