82 lines
4.8 KiB
PHP
82 lines
4.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\InvestmentLegalSource;
|
|
use App\Models\User;
|
|
use App\Support\IsolatedQaDatabase;
|
|
use Brick\Math\BigRational;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/** Exact operation-level IVA calculation against an explicit QA draft; never a payment/return total. */
|
|
final class SimulationFiscalQaPreview
|
|
{
|
|
public function create(User $actor, string $ruleId, array $input, string $key): array
|
|
{
|
|
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
|
|
|
|
return DB::transaction(function () use ($actor, $ruleId, $input, $key): array {
|
|
$rule = app(SimulationTaxRuleGovernance::class)->show($actor, $ruleId);
|
|
abort_unless($rule->status === 'draft' && $rule->rule_code === 'IVA-GERAL-BASE-LIQUIDA', 422,
|
|
'Seleccione o rascunho do primeiro lote fiscal.');
|
|
abort_if(collect($rule->rule_definition['nodes'] ?? [])->contains(fn (array $node): bool => ($node['op'] ?? null) === 'round'), 422,
|
|
'Este percurso preserva o valor exacto e não aplica arredondamento automático.');
|
|
$source = InvestmentLegalSource::findOrFail($rule->legal_source_id);
|
|
$version = $rule->attributesToArray();
|
|
$version['source'] = $source->attributesToArray();
|
|
$date = $input['reference_date'];
|
|
$context = ['operation' => $input['operation'], 'currency' => $input['currency']];
|
|
$missing = [];
|
|
$resolution = [];
|
|
$evaluation = null;
|
|
if ($date < '2026-01-01' || $date > '2026-12-31') {
|
|
$missing[] = 'demonstrated_coverage';
|
|
} else {
|
|
$resolution = app(SimulationTaxParameterResolver::class)->resolveDraftForQa([$version], 'IVA', $date, $date, $context);
|
|
$rate = collect($resolution)->firstWhere('parameter_code', 'IVA_GENERAL_RATE');
|
|
if (($rate['status'] ?? null) !== 'resolved' || ($rate['unit'] ?? null) !== 'percent') {
|
|
$missing[] = 'eligible_parameter';
|
|
} else {
|
|
$evaluation = app(SimulationTaxRuleExpression::class)->evaluate($rule->rule_definition,
|
|
['base_tributavel' => $input['base']], ['iva_general_rate' => $rate['value']]);
|
|
}
|
|
}
|
|
$exact = ($evaluation['status'] ?? null) === 'determinate'
|
|
? (string) BigRational::of($evaluation['result'])->toBigDecimal()
|
|
: null;
|
|
if ($exact === null && $missing === []) {
|
|
$missing = $evaluation['missing'] ?? ['eligible_parameter'];
|
|
}
|
|
$snapshot = [
|
|
'contract_version' => 1, 'scenario_kind' => 'fiscal_rule_qa_preview',
|
|
'base' => $input['base'], 'currency' => $input['currency'], 'indicative_total' => $exact,
|
|
'legal_interpretation' => false, 'formula_version' => 'investmoz.tax.v1',
|
|
'context' => [
|
|
'reference_date' => $date, 'currency' => $input['currency'], 'currency_fraction_digits' => 2,
|
|
'scenario_id' => null, 'scenario_version' => 1, 'engine_version' => 'fiscal-draft-preview-v1',
|
|
'ruleset_version' => $rule->rule_code.':'.$rule->rule_version,
|
|
],
|
|
'result' => [
|
|
'status' => $exact === null ? 'indeterminate' : 'calculated', 'value' => $exact, 'unit' => 'MZN',
|
|
'explanation' => $exact === null
|
|
? 'IVA indeterminado: o contexto, a fonte ou o período não permitem resolver o parâmetro deste lote QA.'
|
|
: 'IVA calculado com precisão decimal exacta, sem arredondamento. Ensaio QA de regra em rascunho; enquadramento declarado, não validado. Não é saldo de declaração nem valor a pagar.',
|
|
'missing_inputs' => $missing, 'source_ids' => [(string) $source->id],
|
|
],
|
|
'presentation' => ['value' => $exact, 'currency' => $input['currency'], 'rounding_applied' => false, 'purpose' => 'operation_tax_not_payment'],
|
|
// Exact rational steps supplement the decimal result; no float conversion.
|
|
'intermediate' => $evaluation,
|
|
'fiscal_provenance' => [
|
|
'mode' => 'qa_draft_only', 'scope_status' => 'user_declared', 'context' => $context,
|
|
'rounding_policy' => $rule->rounding_policy, 'resolution' => $resolution,
|
|
'rule' => $version, 'source_acceptance' => 'Fonte oficial aceite pela decisão Tempo de 08/10/2026; não constitui aprovação jurídica desta operação ou publicação da regra.',
|
|
],
|
|
];
|
|
|
|
return app(SimulationExecution::class)->createFiscalQaPreview($snapshot, $actor->id, $key);
|
|
});
|
|
}
|
|
}
|