83 lines
4.7 KiB
PHP
83 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\InvestmentLegalSource;
|
|
use App\Models\SimulationTaxRuleVersion;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
/** Admission freezes eligibility without granting the public consumer governance write access. */
|
|
final class SimulationPublishedFiscal
|
|
{
|
|
public function availability(): array
|
|
{
|
|
$enabled = (bool) config('simulation-fiscal-consumer.enabled');
|
|
$eligible = $enabled && $this->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): array
|
|
{
|
|
// 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 = [];
|
|
$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);
|
|
}
|
|
}
|