feat: preparar lote IVA exacto em QA sem activacao
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\InvestmentLegalSource;
|
||||
use App\Models\User;
|
||||
use App\Services\SimulationTaxParameterResolver;
|
||||
use App\Services\SimulationTaxRuleExpression;
|
||||
use App\Services\SimulationTaxRuleGovernance;
|
||||
use App\Support\IsolatedQaDatabase;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/** Explicit registration: never run during migration, deployment or document import. */
|
||||
class RegisterFiscalQaDraft extends Command
|
||||
{
|
||||
protected $signature = 'fiscal:qa-register-iva {actor : Existing QA editor ID} {source : Existing accepted legal source UUID}';
|
||||
|
||||
protected $description = 'Cadastrar apenas o rascunho IVA do primeiro lote numa base QA isolada, sem publicação';
|
||||
|
||||
public function handle(SimulationTaxRuleGovernance $governance): int
|
||||
{
|
||||
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 403, 'O cadastro deste lote só é permitido na base QA isolada.');
|
||||
$actor = User::findOrFail($this->argument('actor'));
|
||||
$source = InvestmentLegalSource::findOrFail($this->argument('source'));
|
||||
abort_unless($source->validation_status === 'validated' && $source->diploma === 'Lei 22/2022'
|
||||
&& $source->article === 'Artigo 17(1)', 422, 'Seleccione a referência aceite da taxa geral, Lei 22/2022, artigo 17(1).');
|
||||
$payload = json_decode(file_get_contents(database_path('qa/iva-general-net-v1.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$actualHash = hash_file('sha256', base_path('docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf'));
|
||||
abort_unless(hash_equals($payload['source_sha256'], $actualHash), 422, 'O exemplar não corresponde ao hash da ficha.');
|
||||
$payload['legal_source_id'] = (string) $source->id;
|
||||
app(SimulationTaxRuleExpression::class)->validate($payload['rule_definition']);
|
||||
app(SimulationTaxParameterResolver::class)->validateParameters($payload['parameters']);
|
||||
$existing = $governance->listing($actor, ['rule_code' => $payload['rule_code']])->getCollection()
|
||||
->firstWhere('rule_version', 1);
|
||||
if ($existing) {
|
||||
foreach ($payload as $field => $value) {
|
||||
$stored = $existing->attributesToArray()[$field] ?? null;
|
||||
abort_unless($stored == $value, 409, 'O cadastro existente difere desta ficha; não será sobrescrito.');
|
||||
}
|
||||
abort_unless($existing->status === 'draft', 409, 'A versão existente já não é um rascunho.');
|
||||
$this->line(json_encode(['id' => $existing->id, 'status' => 'draft', 'reused' => true], JSON_THROW_ON_ERROR));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
$rule = $governance->create($actor, $payload);
|
||||
$this->line(json_encode(['id' => $rule->id, 'status' => $rule->status, 'reused' => false], JSON_THROW_ON_ERROR));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\SimulationFiscalQaPreview;
|
||||
use App\Services\SimulationTaxParameterResolver;
|
||||
use App\Services\SimulationTaxRuleExpression;
|
||||
use App\Services\SimulationTaxRuleGovernance;
|
||||
use App\Support\IsolatedQaDatabase;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@@ -13,6 +15,24 @@ use InvalidArgumentException;
|
||||
|
||||
class SimulationTaxRuleGovernanceController extends Controller
|
||||
{
|
||||
public function qaPreview(Request $request, string $id, SimulationFiscalQaPreview $preview): JsonResponse
|
||||
{
|
||||
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
|
||||
$key = $request->header('Idempotency-Key');
|
||||
abort_unless(is_string($key) && strlen($key) >= 16 && strlen($key) <= 128, 428, 'Idempotency-Key é obrigatório.');
|
||||
$input = $request->validate([
|
||||
'base' => ['required', 'string', 'max:40', 'regex:/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/D'],
|
||||
'reference_date' => ['required', 'date_format:Y-m-d'],
|
||||
'currency' => ['required', 'in:MZN'],
|
||||
'operation' => ['required', 'in:internal_general_net'],
|
||||
]);
|
||||
$result = $preview->create($request->user(), $id, $input, $key);
|
||||
$simulation = $result['simulation'];
|
||||
|
||||
return response()->json(['data' => $simulation, 'replayed' => $result['replayed']], $result['replayed'] ? 200 : 202)
|
||||
->header('Location', url('/api/v1/simulations/'.$simulation->id));
|
||||
}
|
||||
|
||||
public function index(Request $request, SimulationTaxRuleGovernance $governance): JsonResponse
|
||||
{
|
||||
$filters = $request->validate([
|
||||
|
||||
+45
-22
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
||||
|
||||
use App\Models\Simulation;
|
||||
use App\Services\SimulationExecution;
|
||||
use App\Support\IsolatedQaDatabase;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -62,29 +63,37 @@ class RunSimulation implements ShouldQueue
|
||||
throw new \RuntimeException('Simulation has no scenario.');
|
||||
}
|
||||
$input = json_decode($scenario->parameters, true, flags: JSON_THROW_ON_ERROR);
|
||||
$row = DB::selectOne('select (CAST(? AS numeric) * CAST(? AS numeric) / 100 * (1 - CAST(? AS numeric) / 100))::text as value', [$input['base'], $input['rate'], $input['reduction']]);
|
||||
$resultId = (string) Str::uuid();
|
||||
$engineVersion = 'portal-arithmetic-v1+simulation-v'.$simulation->version_no;
|
||||
$result = ['base' => $input['base'], 'rate_percent' => $input['rate'], 'reduction_percent' => $input['reduction'], 'scenario_kind' => 'custom_planning', 'indicative_total' => $row->value,
|
||||
'currency' => null, 'legal_interpretation' => false, 'formula_version' => 'portal-arithmetic-v1',
|
||||
'contract_version' => 1,
|
||||
'context' => [
|
||||
'reference_date' => substr((string) $scenario->created_at, 0, 10),
|
||||
'currency' => null,
|
||||
'currency_fraction_digits' => null,
|
||||
'scenario_id' => (string) $scenario->id,
|
||||
'scenario_version' => (int) $scenario->version_no,
|
||||
'engine_version' => $engineVersion,
|
||||
'ruleset_version' => null,
|
||||
],
|
||||
'result' => [
|
||||
'status' => 'calculated',
|
||||
'value' => (string) $row->value,
|
||||
'unit' => null,
|
||||
'explanation' => 'Resultado aritmético determinístico; não constitui cálculo nem conclusão fiscal.',
|
||||
'missing_inputs' => [],
|
||||
'source_ids' => [],
|
||||
]];
|
||||
if (($input['scenario_kind'] ?? null) === 'fiscal_rule_qa_preview') {
|
||||
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
|
||||
$result = $input['fiscal_preview'];
|
||||
$engineVersion = 'fiscal-draft-preview-v1';
|
||||
$result['context']['scenario_id'] = (string) $scenario->id;
|
||||
$result['context']['scenario_version'] = (int) $scenario->version_no;
|
||||
} else {
|
||||
$row = DB::selectOne('select (CAST(? AS numeric) * CAST(? AS numeric) / 100 * (1 - CAST(? AS numeric) / 100))::text as value', [$input['base'], $input['rate'], $input['reduction']]);
|
||||
$result = ['base' => $input['base'], 'rate_percent' => $input['rate'], 'reduction_percent' => $input['reduction'], 'scenario_kind' => 'custom_planning', 'indicative_total' => $row->value,
|
||||
'currency' => null, 'legal_interpretation' => false, 'formula_version' => 'portal-arithmetic-v1',
|
||||
'contract_version' => 1,
|
||||
'context' => [
|
||||
'reference_date' => substr((string) $scenario->created_at, 0, 10),
|
||||
'currency' => null,
|
||||
'currency_fraction_digits' => null,
|
||||
'scenario_id' => (string) $scenario->id,
|
||||
'scenario_version' => (int) $scenario->version_no,
|
||||
'engine_version' => $engineVersion,
|
||||
'ruleset_version' => null,
|
||||
],
|
||||
'result' => [
|
||||
'status' => 'calculated',
|
||||
'value' => (string) $row->value,
|
||||
'unit' => null,
|
||||
'explanation' => 'Resultado aritmético determinístico; não constitui cálculo nem conclusão fiscal.',
|
||||
'missing_inputs' => [],
|
||||
'source_ids' => [],
|
||||
]];
|
||||
}
|
||||
$json = json_encode($result, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||
$canonicalResult = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$json])->canonical;
|
||||
$resultHash = hash('sha256', $canonicalResult);
|
||||
@@ -96,11 +105,25 @@ class RunSimulation implements ShouldQueue
|
||||
DB::table('simulation_jobs')->where('id', $job->id)->update(['status' => 'succeeded', 'finished_at' => now(), 'updated_at' => now()]);
|
||||
DB::table('simulation_reports')->insert(['id' => (string) Str::uuid(), 'simulation_id' => $simulation->id, 'version_no' => $scenario->version_no,
|
||||
'status' => 'ready', 'report_snapshot' => $json, 'report_sha256' => $resultHash, 'created_at' => now(), 'updated_at' => now()]);
|
||||
$sourceJson = json_encode($input, JSON_THROW_ON_ERROR);
|
||||
$sourceJson = json_encode(isset($result['fiscal_provenance']) ? [
|
||||
'base' => $result['base'], 'currency' => $result['currency'],
|
||||
'reference_date' => $result['context']['reference_date'],
|
||||
'context' => $result['fiscal_provenance']['context'], 'scope_status' => 'user_declared',
|
||||
] : $input, JSON_THROW_ON_ERROR);
|
||||
$canonicalSource = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$sourceJson])->canonical;
|
||||
DB::table('simulation_result_sources')->insertOrIgnore(['id' => (string) Str::uuid(), 'result_id' => $resultId,
|
||||
'source_type' => 'user_input', 'source_id' => $scenario->id, 'source_version' => (string) $scenario->version_no,
|
||||
'source_snapshot' => $sourceJson, 'source_sha256' => hash('sha256', $canonicalSource), 'created_at' => now()]);
|
||||
if (isset($result['fiscal_provenance'])) {
|
||||
$provenance = $result['fiscal_provenance'];
|
||||
$provenanceJson = json_encode($provenance, JSON_THROW_ON_ERROR);
|
||||
$canonicalProvenance = DB::selectOne('select CAST(? AS jsonb)::text as canonical', [$provenanceJson])->canonical;
|
||||
DB::table('simulation_result_sources')->insert([
|
||||
'id' => (string) Str::uuid(), 'result_id' => $resultId, 'source_type' => 'tax_rule_draft',
|
||||
'source_id' => $provenance['rule']['id'], 'source_version' => (string) $provenance['rule']['rule_version'],
|
||||
'source_snapshot' => $provenanceJson, 'source_sha256' => hash('sha256', $canonicalProvenance), 'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
DB::table('simulation_audit_events')->insert(['simulation_id' => $simulation->id, 'actor_user_id' => $simulation->owner_user_id,
|
||||
'action' => 'simulation.completed', 'correlation_id' => $simulation->id, 'data' => json_encode(['result_sha256' => hash('sha256', $json), 'attempt' => $job->attempts + 1], JSON_THROW_ON_ERROR), 'occurred_at' => now()]);
|
||||
}, 3);
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Services;
|
||||
|
||||
use App\Jobs\RunSimulation;
|
||||
use App\Models\Simulation;
|
||||
use App\Support\IsolatedQaDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -17,11 +18,27 @@ final class SimulationExecution
|
||||
*/
|
||||
public function create(array $data, ?int $actorId, ?string $organizationId, ?string $token, string $key): array
|
||||
{
|
||||
$tokenHash = $actorId === null ? hash_hmac('sha256', (string) $token, (string) config('app.key')) : null;
|
||||
$scope = $actorId !== null ? (string) $actorId : ($tokenHash ?? 'anonymous');
|
||||
$scopeHash = hash('sha256', $scope);
|
||||
$keyHash = hash('sha256', strtolower($key));
|
||||
$payload = ['base' => (string) $data['base'], 'rate' => (string) $data['rate'], 'reduction' => (string) $data['reduction'], 'scenario_kind' => 'custom_planning', 'organization_id' => $organizationId];
|
||||
|
||||
return $this->createPayload($payload, $actorId, $organizationId, $token, $key);
|
||||
}
|
||||
|
||||
public function createFiscalQaPreview(array $snapshot, int $actorId, string $key): array
|
||||
{
|
||||
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
|
||||
abort_unless(($snapshot['scenario_kind'] ?? null) === 'fiscal_rule_qa_preview'
|
||||
&& in_array($snapshot['result']['status'] ?? null, ['calculated', 'indeterminate'], true)
|
||||
&& ($snapshot['presentation']['rounding_applied'] ?? null) === false, 422);
|
||||
|
||||
return $this->createPayload(['scenario_kind' => 'fiscal_rule_qa_preview', 'fiscal_preview' => $snapshot, 'organization_id' => null],
|
||||
$actorId, null, null, $key);
|
||||
}
|
||||
|
||||
private function createPayload(array $payload, ?int $actorId, ?string $organizationId, ?string $token, string $key): array
|
||||
{
|
||||
$tokenHash = $actorId === null ? hash_hmac('sha256', (string) $token, (string) config('app.key')) : null;
|
||||
$scopeHash = hash('sha256', $actorId !== null ? (string) $actorId : ($tokenHash ?? 'anonymous'));
|
||||
$keyHash = hash('sha256', strtolower($key));
|
||||
ksort($payload);
|
||||
$payloadHash = hash('sha256', json_encode($payload, JSON_THROW_ON_ERROR));
|
||||
|
||||
@@ -44,7 +61,9 @@ final class SimulationExecution
|
||||
$simulation = Simulation::create([
|
||||
'id' => $id, 'questionnaire_version_id' => '61000100-0000-4000-8000-000000000001', 'owner_user_id' => $actorId, 'organization_id' => $organizationId,
|
||||
'anonymous_token_hash' => $tokenHash, 'status' => 'queued', 'version_no' => 1,
|
||||
'input_snapshot' => $payload, 'calculation_snapshot' => ['engine' => 'portal-arithmetic-v1', 'formula' => 'base * rate / 100 * (1 - reduction / 100)', 'legal_rate_source' => null],
|
||||
'input_snapshot' => $payload, 'calculation_snapshot' => $payload['scenario_kind'] === 'fiscal_rule_qa_preview'
|
||||
? ['engine' => 'fiscal-draft-preview-v1', 'mode' => 'qa_draft_only', 'arithmetic' => 'exact_decimal_no_rounding']
|
||||
: ['engine' => 'portal-arithmetic-v1', 'formula' => 'base * rate / 100 * (1 - reduction / 100)', 'legal_rate_source' => null],
|
||||
]);
|
||||
$scenarioId = (string) Str::uuid();
|
||||
DB::table('simulation_scenarios')->insert([
|
||||
@@ -85,6 +104,8 @@ final class SimulationExecution
|
||||
return DB::transaction(function () use ($id, $scenarioId, $data, $request): Simulation {
|
||||
$this->setContext($request->user()?->id, $this->tokenHash($request));
|
||||
$simulation = Simulation::whereNull('deleted_at')->lockForUpdate()->findOrFail($id);
|
||||
abort_if(($simulation->input_snapshot['scenario_kind'] ?? null) === 'fiscal_rule_qa_preview', 422,
|
||||
'Crie uma nova pré-visualização fiscal; este snapshot conserva a regra e as entradas originais.');
|
||||
$this->requireMatch($request, $simulation);
|
||||
$scenario = DB::table('simulation_scenarios')->where('simulation_id', $id)->where('id', $scenarioId)->lockForUpdate()->first();
|
||||
abort_if($scenario === null, 404);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Support\IsolatedQaDatabase;
|
||||
use Brick\Math\BigRational;
|
||||
use DateTimeImmutable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -126,14 +127,30 @@ final class SimulationTaxParameterResolver
|
||||
* @return list<array<string,mixed>>
|
||||
*/
|
||||
public function resolveAvailableParameters(array $versions, string $component, string $periodStart, string $periodEnd, array $context): array
|
||||
{
|
||||
return $this->resolveWithStatus($versions, $component, $periodStart, $periodEnd, $context, 'published');
|
||||
}
|
||||
|
||||
/** Explicit draft preview. Never used by the published-parameter API. */
|
||||
public function resolveDraftForQa(array $versions, string $component, string $periodStart, string $periodEnd, array $context): array
|
||||
{
|
||||
abort_unless(IsolatedQaDatabase::allowsSchemaChanges(), 404);
|
||||
|
||||
return $this->resolveWithStatus($versions, $component, $periodStart, $periodEnd, $context, 'draft');
|
||||
}
|
||||
|
||||
private function resolveWithStatus(array $versions, string $component, string $periodStart, string $periodEnd, array $context, string $requiredStatus): array
|
||||
{
|
||||
$start = $this->date($periodStart);
|
||||
$end = $this->date($periodEnd);
|
||||
if ($end < $start || $start->diff($end)->days > 365) {
|
||||
throw new InvalidArgumentException('Reveja o período de aplicação indicado.');
|
||||
}
|
||||
$groups = [];
|
||||
|
||||
foreach ($versions as $version) {
|
||||
if (($version['component'] ?? null) !== $component
|
||||
|| ($version['status'] ?? null) !== 'published'
|
||||
|| ($version['status'] ?? null) !== $requiredStatus
|
||||
|| ($version['source']['validation_status'] ?? null) !== 'validated'
|
||||
|| (int) ($version['source_version_no'] ?? 0) !== (int) ($version['source']['version_no'] ?? -1)
|
||||
|| ! $this->overlaps($version, $start, $end)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"rule_code": "IVA-GERAL-BASE-LIQUIDA",
|
||||
"rule_version": 1,
|
||||
"component": "IVA",
|
||||
"scope_key": "mz.internal-general-net.mzn",
|
||||
"valid_from": "2023-01-01",
|
||||
"valid_until": null,
|
||||
"source_sha256": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53",
|
||||
"rule_definition": {
|
||||
"language": "investmoz.tax.v1",
|
||||
"nodes": {
|
||||
"base": {"op": "input", "key": "base_tributavel"},
|
||||
"rate": {"op": "parameter", "key": "iva_general_rate"},
|
||||
"hundred": {"op": "literal", "value": "100"},
|
||||
"product": {"op": "multiply", "args": ["base", "rate"]},
|
||||
"intermediate": {"op": "divide", "args": ["product", "hundred"]}
|
||||
},
|
||||
"result": "intermediate"
|
||||
},
|
||||
"parameters": {
|
||||
"IVA_GENERAL_RATE": {
|
||||
"value": "16",
|
||||
"unit": "percent",
|
||||
"conditions": {"operation": "internal_general_net", "currency": "MZN"}
|
||||
}
|
||||
},
|
||||
"rounding_policy": {
|
||||
"status": "not_applied",
|
||||
"precision": "exact_decimal",
|
||||
"method": "none",
|
||||
"ties": "not_applicable_without_rounding",
|
||||
"aggregation": "single_declared_taxable_base",
|
||||
"purpose": "operation_tax_not_payment",
|
||||
"evidence": "Orientação expressa Tempo: calcular este lote IVA sem arredondamento automático; ficha v1.2",
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31"
|
||||
}
|
||||
}
|
||||
@@ -1,122 +1,86 @@
|
||||
# IVA-GERAL-BASE-LIQUIDA — ficha documental v1.1
|
||||
# IVA-GERAL-BASE-LIQUIDA — ficha documental v1.2
|
||||
|
||||
Data: 21/10/2026. Referência: AUD-SIM-INT02 na matriz existente. Origem: proposta `0fafd348`; preparação solicitada pela Tempo. Estado: **rascunho documental para revisão; não cadastrado, não publicado, não activo**. Esta versão identifica fundamento e lacunas; não certifica uma regra executável completa.
|
||||
21/10/2026. AUD-SIM-INT02 na matriz existente. Proposta inicial `0fafd348`; v1 em `325d58b6`, v1.1 em `37085b40`. Essas versões ficam preservadas em Git. Esta versão aplica a orientação posterior expressa da Tempo: **prosseguir neste lote IVA com precisão decimal exacta, sem introduzir arredondamento automático; separar valor calculado de apresentação monetária**. Retira a falta de uma política genérica de arredondamento como condição bloqueante do cálculo. Não estende a decisão a todos os impostos.
|
||||
|
||||
## Âmbito e período
|
||||
**Estado:** cadastro e cálculo experimental em QA, regra em rascunho, sem publicação/activação ou integração em `main`. Não equivale a conclusão do simulador fiscal, emissão de factura, declaração ou determinação de saldo a pagar. A Tempo declarou não ter conferido a ficha local nem validado juridicamente as conclusões transcritas; não se lhe atribui essa validação.
|
||||
|
||||
Percurso proposto: base tributável líquida identificada × taxa geral / 100, para operação interna abrangida pela taxa geral de IVA, em MZN explicitamente indicado. Cobertura de ensaio proposta: 01/01/2026–31/12/2026. É uma delimitação técnica do lote, não uma data de caducidade da lei nem uma restrição organizacional.
|
||||
## Âmbito e contrato
|
||||
|
||||
Não determina automaticamente incidência, qualidade do sujeito passivo, localização, isenção ou regime especial. A base fiscal não pode ser deduzida de uma rubrica de orçamento. Âmbito apenas declarado pelo utilizador deve aparecer como declarado; ausência ou incompatibilidade produz indeterminação. A fundamentação da taxa não prova a sujeição de uma operação concreta.
|
||||
Um único percurso: base tributável líquida já identificada × taxa geral /100, em MZN explícito, para operação interna abrangida pela taxa geral. Janela demonstrada do lote:01/01/2026–31/12/2026, delimitação técnica que não cria caducidade legal em31/12. Não decide automaticamente incidência, sujeito passivo, localização, isenção ou regime especial. Contexto declarado é rotulado como tal; uma rubrica orçamental não se transforma automaticamente em base fiscal.
|
||||
|
||||
Excluídos: imposto já incluído no preço, taxas reduzidas, isenções/listas, importações, digital/não residentes, deduções, crédito/reembolso, incentivos territoriais e outros impostos. Permanecem no inventário existente, sem declaração de cobertura deste lote.
|
||||
Excluídos: extracção de preço com IVA incluído, taxas reduzidas, isenções/listas, importações, digital/não residentes, dedução/crédito/reembolso, incentivos e outros impostos. Permanecem no inventário documental existente, sem omissão nem alegação de cobertura deste lote.
|
||||
|
||||
## Fontes e aplicação
|
||||
|
||||
As fontes oficiais são aceites pela decisão da Tempo de 08/10/2026. Os vínculos abaixo são rastreabilidade e aplicação das fontes; não se exige nova certificação de proveniência. Reutilizar [análise primária](primary-text-review-2025-2026.md), [registo de fontes](official-source-register.md) e [análise da reforma](fiscal-reform-2026-review.md). Exigências históricas genéricas de autenticação adicional ou de um novo papel organizacional não prevalecem sobre essa decisão.
|
||||
|
||||
| Elemento | Fundamento / exemplar | Consequência nesta ficha |
|
||||
|---|---|---|
|
||||
| Taxa geral 16% | [Lei22/2022 republicada](source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf), art.17(1), página3 do PDF, BR2252-(27). SHA256 `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53`. | Valor nominal fundamentado; ainda não é parâmetro executável cadastrado. |
|
||||
| Eficácia desde 01/01/2023 | Mesmo exemplar, art.6 da Lei22/2022, página4 do PDF. | Preservar início legal; não substituir pelo início da cobertura de ensaio de2026. |
|
||||
| Alterações para2026 | [BR248, suplemento1/2025](source-archive/BR-248-I-Suplemento-1-2025.pdf), Lei10/2025, art.1 na página12 e art.5 na página15. SHA256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. | Eficácia em01/01/2026; lista de artigos alterados não inclui17. Não equivale a consolidação completa de incidência/regimes. |
|
||||
| Preço com imposto incluído | Mesmo BR248, art.34 do CIVA, página14: divisão por116 ×100 e arredondamento do resultado para a unidade mais próxima. | Regula extracção da base a partir do preço com imposto. Fora deste percurso. Não fundamenta arredondamento do imposto calculado sobre base líquida; não inferir tratamento de empates. |
|
||||
| Rectificação de2017 | [BR89/2017](source-archive/BR-89-I-Serie-2017-06-08.pdf), aviso relativo à Lei13/2016; análise existente identifica art.6(5)c/d,14(2)c e anexos de isenção. | Preservar cadeia documental. A taxa geral não dispensa determinar enquadramento da operação. Listas de isenção não foram consolidadas por esta ficha. |
|
||||
|
||||
Foi recuperado do inventário existente o exemplar `Lei 13_2016_Reimpressao Codigo do IVA.pdf`, em `\\Mac\Home\Documents\TEMPO\Portal Tempo\Legislacao\`, 32páginas, 2.127.554bytes, SHA256 `B90DC7C14ECEA2E378C4A62E8E5A47D54A5644C457BAEB8B8053B92FD2A5710B`. É digitalizado, sem camada textual nas32páginas. A identificação e integridade foram registadas; não se afirma leitura integral nem fundamento de âmbito/arredondamento a partir deste exemplar nesta versão. Não o confundir com outra cópia de15páginas mencionada em análises anteriores. O cotejo visual dos dispositivos relevantes e das alterações/regulamentação aplicáveis continua trabalho documental concreto, sem revalidar a credibilidade oficial nem repetir todo o inventário.
|
||||
|
||||
## Parâmetros, lacunas e contrato de resultado
|
||||
|
||||
| Campo | Situação |
|
||||
| Campo | Estado actual |
|
||||
|---|---|
|
||||
| Identificador / versão | `IVA-GERAL-BASE-LIQUIDA` / documental-v1.1; identificador provisório, sem UUID de base. |
|
||||
| Taxa / unidade | 16 / percent, fundamento acima. |
|
||||
| Base / moeda / data | Entradas obrigatórias, com origem e contexto; MZN explícito, sem conversão implícita. Não substituir ausência por zero. |
|
||||
| Enquadramento | Identificação de operação interna à taxa geral e estado declarado/comprovado. Falta fechar correspondência normativa das condições do percurso. |
|
||||
| Arredondamento | **Indeterminado nesta versão:** precisão, modo, empates e incidência por linha/documento/apuramento não fundamentados para base líquida. Não adoptar HALF_UP ou duas casas por conveniência técnica. |
|
||||
| Expressão conceptual | `base_tributavel * taxa_percentual / 100`; não constitui definição DSL publicada. Conservar exactidão intermédia; sem total fiscal final enquanto faltar política aplicável. |
|
||||
| Cobertura | Ensaio2026; vigência legal separada e sem expiração artificial. Fora da cobertura demonstrada é mensagem de cobertura, não revogação da lei. |
|
||||
| Proveniência | Resultado deverá fixar regra/versão, fonte/hash/artigo, data/período, entradas/unidades, contexto e seu estado, passos, arredondamento e lacunas. |
|
||||
| Regra | `IVA-GERAL-BASE-LIQUIDA`, versão1, estado `draft`; cadastro explícito apenas em QA. |
|
||||
| Taxa / unidade | `16` / `percent`, vínculo Lei22/2022, art.17(1), exemplar/hash abaixo. |
|
||||
| Vigência cadastrada | Desde01/01/2023, sem termo artificial; cobertura ensaiada2026 separada da vigência. |
|
||||
| Entradas | Base decimal como texto, MZN explícito, data de exigibilidade declarada, contexto `internal_general_net`. Zero explícito é válido; ausência não é zero. |
|
||||
| Valor calculado | Decimal exacto obtido pelo avaliador racional existente; sem conversão para float, truncagem ou redução para duas casas. |
|
||||
| Apresentação | Valor decimal preservado como texto e moeda separados. Duas casas da unidade monetária não impõem arredondamento ao cálculo. Ecrã/CSV/dossier reproduzem o valor guardado. |
|
||||
| Arredondamento | `not_applied`; nenhum nó `round` neste percurso. Não se inventa precisão, empate ou método fiscal. |
|
||||
| Indeterminação efectiva | Fonte/versão/contexto incompatível, parâmetro ausente ou pedido fora da cobertura demonstrada. A falta de arredondamento não torna indeterminado o resultado deste percurso. |
|
||||
| Limites do resultado | Cálculo QA sobre regra em rascunho e enquadramento declarado; não é obrigação validada nem saldo a pagar. |
|
||||
|
||||
Casos documentais iniciais, sem pretender validar incidência: base100MZN e taxa16% dão intermédio exacto16MZN; base0,01MZN dá intermédio0,0016MZN, cujo valor fiscal final fica indeterminado sem política. São referências aritméticas, não provas de obrigação fiscal. Acrescentar casos independentes de fronteira e empates depois de fundamentar a política.
|
||||
## Fontes reutilizadas e disposições documentadas
|
||||
|
||||
## Integração e estimativa separadas
|
||||
Fontes oficiais aceites pela decisão Tempo de08/10/2026; sem nova certificação genérica de proveniência. A identificação de âmbito, período e dispositivo é aplicação da fonte aceite. Reutilizar [análise primária](primary-text-review-2025-2026.md), [registo de fontes](official-source-register.md) e [reforma fiscal](fiscal-reform-2026-review.md). A aceitação da fonte não activa regra nem valida juridicamente uma operação concreta.
|
||||
|
||||
Reutilizar `SimulationTaxRuleGovernance`, `SimulationTaxParameterResolver` e `SimulationTaxRuleExpression`. Não reconstruir governação, resolvedor temporal ou avaliador. O fluxo actual de `RunSimulation` é planeamento personalizado; não o renomear como resultado fiscal verificado.
|
||||
| Exemplar | Identificação e integridade |
|
||||
|---|---|
|
||||
| [Lei13/2016 / CIVA republicado](source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf) | BR156,10.º suplemento,30/12/2016;32páginas,2.127.554bytes; SHA256 `B90DC7C14ECEA2E378C4A62E8E5A47D54A5644C457BAEB8B8053B92FD2A5710B`. Leitura visual dirigida PDF6–8,13–14,17–18; entrada em vigor no art.4/PDF5. Não se afirma análise integral de anexos. |
|
||||
| [Lei22/2022 republicada](source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf) | BR250,2.º suplemento;18páginas,1.834.284bytes; SHA256 `EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53`. Art.17(1),PDF3/BR2252-(27), taxa16%; art.6 da Lei,PDF4, eficácia01/01/2023. |
|
||||
| [BR248, suplemento1/2025](source-archive/BR-248-I-Suplemento-1-2025.pdf) |21páginas,440.018bytes; SHA256 `CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251`. Lei10/2025,PDF12–15; eficácia01/01/2026. |
|
||||
| [BR16/2008 / Decreto7/2008](source-archive/BR-16-I-Serie-2008-04-16.pdf) |16/04/2008;28páginas,2.267.865bytes; SHA256 `FC373FF5637EDF75EC6A9595DEC8763A8503D4EE786E3B7D8F1C742EAE9D80EE`. Regulamento IVA,PDF1–2; página2 conferida visualmente. O BR inclui outros impostos, que não são fundamento deste lote. |
|
||||
| [BR89/2017 / rectificação CIVA](source-archive/BR-89-I-Serie-2017-06-08.pdf) | Aviso relativo a art.6(5)c/d,14(2)c e anexos de isenção; análise anterior reutilizada. Não encerra consolidação das listas de isenção. |
|
||||
|
||||
| Frente | Trabalho restante do lote | Estimativa proposta |
|
||||
| Disposição exacta | Conteúdo documentado / consequência |
|
||||
|---|---|
|
||||
| CIVA art.1(1)(a), PDF6, BR1426-(264) | Transmissões de bens e prestações de serviços onerosas no território nacional, nos termos do art.6, por sujeito passivo nessa qualidade. Exigir identificação desse contexto; importações ficam fora. |
|
||||
| Art.2(1)(a), PDF6 | Qualidade do sujeito passivo ligada às condições de residência/estabelecimento e actividade independente previstas. Perfil de investidor não prova sujeição. |
|
||||
| Arts.3(1),4(1), PDF6–7 | Transferência onerosa de bens corpóreos e noção geral de serviço. Casos assimilados/especiais não entram implicitamente na fórmula. |
|
||||
| Art.6(1), PDF7, BR1426-(265) | Para bens, localização no início do transporte/expedição ou quando postos à disposição, sem transporte. Serviços seguem art.6(3) e excepções subsequentes; não basta localização do prestador para todos os serviços. |
|
||||
| Art.15(1), PDF13, BR1426-(271) | Base geral: contraprestação obtida ou a obter, sem prejuízo das bases especiais do n.º2. Não equivale a qualquer receita de planeamento. |
|
||||
| Art.15(5)(a)/(b), PDF14, BR1426-(272) | Inclusão de outros impostos/direitos/taxas, excepto o próprio IVA, e despesas acessórias debitadas, nas condições previstas. |
|
||||
| Art.15(6)(a)–(d), PDF14 | Exclusões condicionadas: indemnizações judiciais indicadas, descontos/abatimentos/bónus, pagamentos em nome/conta de terceiros registados adequadamente e embalagens retornáveis. Não classificar só pelo nome da rubrica. |
|
||||
| Art.17(2), PDF14; art.7(1)(a)/(b), PDF8 | Taxa vigente na exigibilidade; facto gerador/exigibilidade diferem por operação e condições. A criação da simulação não substitui a data fiscal. Adiantamentos e prestações continuadas requerem enquadramento próprio. |
|
||||
| Art.27(5)(c)/(d), PDF17, BR1426-(275) | Separação do preço líquido/elementos da base, taxa e montante do imposto. Lei10/2025 preserva essas alíneas com «[…]», alterando o n.º10. |
|
||||
| Regulamento, arts.4/5, Decreto7/2008,PDF1–2 | Remete matéria colectável para arts.15/16 e apuramento devido para arts.18–22 do Código. IVA da operação não equivale ao saldo após deduções. |
|
||||
|
||||
Cadeia temporal documentada: Lei13/2016 efectiva em01/01/2017; a taxa histórica17% do exemplar não é usada para2026. Lei22/2022 altera art.17(1) para16% desde01/01/2023 e acrescenta art.15(10) para taxa reduzida, mantendo os números gerais consultados. Lei10/2025, efectiva01/01/2026, não lista arts.15/17 entre os alterados. Esta conclusão documental não é atribuída como validação jurídica ao utilizador e não certifica todos os regimes/excepções.
|
||||
|
||||
## Precisão do cálculo e exigências contextuais
|
||||
|
||||
Art.34 do CIVA, Lei10/2025/PDF14, regula extracção de base de valores com imposto incluído, com divisor116 e arredondamento para unidade mais próxima. Art.9(1)(b) do regulamento/Decreto7/2008,PDF2/p.100, trata do valor do meio de pagamento por cheque ou vale de correio e arredondamento por excesso para meticais nesse exemplar histórico. **Nenhum é aplicado como regra geral de arredondamento deste cálculo sobre base líquida.** A vigência consolidada e exigências de formatos de pagamento/declaração serão determinadas quando esses consumidores forem abrangidos.
|
||||
|
||||
A [comunicação oficial de30/09/2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/) anuncia alterações regulamentares; não fornece articulado publicado e eficácia por disposição. A pesquisa dirigida em fontes oficiais não identificou uma exigência geral que obrigue a arredondar este percurso. Ausência de correspondência não prova inexistência de instruções específicas. Não há questão pendente à Tempo como pré-condição deste cálculo. Se um futuro formato exigir precisão, apresentar uma única questão contextual, identificando o formato, precisão, método, empates e agregação; não generalizar a outros cálculos.
|
||||
|
||||
Nota de rastreabilidade, sem reabrir pontos: o exemplar BR54 de06/04/2017 consultado anteriormente não continha a alteração regulamentar procurada. O [catálogo INM](https://www.inm.gov.mz/pt-br/produtos?page=124) identifica Decreto8/2017 no BR50 de30/03/2017. O articulado e alterações pertinentes continuam a ser correspondências documentais a completar quando necessárias ao consumidor; não são bloqueio genérico do cálculo exacto deste lote.
|
||||
|
||||
## Exemplos e provas dirigidas
|
||||
|
||||
Pressupostos explícitos: operação interna onerosa à taxa geral, base já apurada, sem isenção/redução/base especial, data coberta2026 e MZN. Não são classificações jurídicas de operações reais.
|
||||
|
||||
| Base | IVA calculado exacto | Apresentação / significado |
|
||||
|---|---|---|
|
||||
| Aplicação documental e cadastro | Fechar condições e arredondamento com citação; completar versão e casos; transcrever parâmetros/vínculos como rascunho QA quando autorizado. Cadastro não exige criar um novo motor. | 4–8h |
|
||||
| Código e aceitação técnica | Orquestrar contexto/período → resolvedor → avaliador; resultado e proveniência imutáveis; ligar interface, dossier, histórico e relatórios; ensaios dirigidos. Confirmar necessidade real de alteração de schema antes de a propor. | 12–20h |
|
||||
|100|16|`16` e moeda `MZN`, separados; não é saldo a pagar. |
|
||||
|0,03|0,0048|Conservar todas as casas no armazenamento, ecrã e exportação. Não substituir por0,00. |
|
||||
|0|0|Zero explicitamente declarado, distinto de entrada ausente. |
|
||||
|9007199254740993,0003|1441151880758558,880048|Preservar sem converter para float JavaScript/PHP. |
|
||||
|100 + transporte10 − desconto5|16,8 sobre base105|Apenas se verificadas as condições de inclusão/exclusão do art.15. |
|
||||
|
||||
Total original:16–28h, estimativa condicional, não medição de horas já consumidas nem novo compromisso de prazo. Espera por elemento documental concreto, revisão e activação não incluídas. Esta ficha não reduz automaticamente a estimativa; recalibrar após fechar as duas lacunas identificadas. As antigas1–2h eram exclusivamente do recorte de exportações já concluído.
|
||||
Os testes do novo consumidor cobrem registo repetido sem duplicação, rascunho excluído da API publicada, resultado exacto, snapshot/relatório conservados após edição da regra e recuperação noutra sessão de autenticação, acesso negado a outra conta, moeda obrigatória, incompatibilidade da fonte, cobertura e recusa fora de QA. A nova sessão provada é API/autenticação, não alegação de novo ensaio browser. A conta PostgreSQL QA ultrapassa RLS: recusas demonstram a política da aplicação, não nova prova de RLS.
|
||||
|
||||
## Aceitação antes de publicação
|
||||
## Implementação, limites e integração
|
||||
|
||||
1. Condições, período e política de arredondamento ligados aos dispositivos aplicáveis; lacunas resolvidas sem valores inventados.
|
||||
2. No QA isolado, resolução inequívoca por contexto/data, recusa explícita de ausência, ambiguidade e cobertura incompatível; preservar mecanismos temporais existentes.
|
||||
3. Casos independentes exactos, fraccionários e de fronteira da política; distinguir zero de ausência e exigir moeda/unidades.
|
||||
4. Snapshot reprodutível com fontes e versões; nova sessão preserva resultado histórico; verificar apenas o novo consumidor quanto ao isolamento.
|
||||
5. Ecrã e relatório distinguem estimativas do utilizador, contexto declarado e cálculo com regra verificada. Conferir apenas novas linhas/deltas; reutilizar provas aceites de valores, CSV, exportações e paginação.
|
||||
6. Revisão e publicação pelos mecanismos/papéis existentes da Tempo, separadas do cadastro e da publicação documental. Nenhuma activação nesta preparação.
|
||||
Reutilizados `SimulationTaxRuleGovernance`, `SimulationTaxParameterResolver`, `SimulationTaxRuleExpression` e o armazenamento existente de simulações, fontes, histórico e relatórios. Não há motor novo, migration ou alteração da base activa. `fiscal:qa-register-iva` exige alvo QA isolado, editor e referência de fonte já existente; confere hash e cadastra apenas rascunho, de forma idempotente. Não corre em deploy/importação. O artefacto versionado é `database/qa/iva-general-net-v1.json`.
|
||||
|
||||
A entrega `13f68f1b` — melhorias de planeamento financeiro, recuperação, exportação e idioma — foi integrada após autorização da Tempo e não depende destas lacunas fiscais. `0fafd348` e esta ficha permanecem na branch fiscal, separados de `main`.
|
||||
Pré-visualização administrativa `POST /api/v2/investment/admin/tax-rules/{id}/qa-preview`, com `Idempotency-Key`, exige os controlos administrativos existentes e alvo QA isolado. Não muda a API de parâmetros publicados para aceitar rascunhos. O snapshot congela regra, fonte/versão/hash, contexto declarado, data, passos racionais, decimal calculado, apresentação sem arredondamento e limitações. Alteração da regra não reescreve o histórico. Uma nova entrada produz nova execução; um snapshot fiscal não é convertido por edição numa simulação de planeamento.
|
||||
|
||||
## Adenda v1.1 — leitura dirigida de enquadramento e arredondamento
|
||||
O cadastro persistido usa clone `simulator_fiscal_delta_20261021_test` e referência/actores sintéticos explicitamente identificados; não representa revisão jurídica por uma pessoa real nem atribuição persistente de papéis da Tempo. O recibo privado regista IDs, estado draft, zero regras publicadas e hash do relatório. A conta técnica e os ficheiros de prova privados não são material de deploy.
|
||||
|
||||
21/10/2026. Esta adenda substitui o estado de «não consultado» dos dispositivos abaixo, mantendo o histórico da v1 nas secções anteriores. Não atribui ao utilizador conferência local nem validação jurídica da ficha. Não altera a base de dados, o código, a governação ou o estado de publicação.
|
||||
Planeamento personalizado mantém identificação própria. O resultado deste lote é apresentado como cálculo QA com regra em rascunho, não estimativa percentual introduzida pelo utilizador nem regra fiscal activada. As provas aceites de13f68f1b/valores/CSV/paginação não são repetidas. A entrega anterior permanece independente e concluída; não integra esta branch automaticamente.
|
||||
|
||||
### Exemplares efectivamente consultados
|
||||
## Estimativa e revisão
|
||||
|
||||
- [CIVA republicado pela Lei13/2016, BR156, 10.º suplemento, 30/12/2016](source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf): cópia byte a byte do exemplar já inventariado acima, mesmo hash `B90DC7C14ECEA2E378C4A62E8E5A47D54A5644C457BAEB8B8053B92FD2A5710B`. Leitura visual dirigida das páginas6–8,13–14,17–18; folhas de contacto para localizar dispositivos. Não se afirma análise integral dos anexos.
|
||||
- [BR16, I série, 16/04/2008, Decreto7/2008 e regulamento anexo](source-archive/BR-16-I-Serie-2008-04-16.pdf): recuperado do inventário existente, 28páginas, 2.267.865bytes, SHA256 `FC373FF5637EDF75EC6A9595DEC8763A8503D4EE786E3B7D8F1C742EAE9D80EE`. Texto pesquisável; página2 conferida visualmente para distinguir pagamento de liquidação. O BR contém também outros impostos: resultados de pesquisa fora do regulamento IVA não são fundamento deste lote.
|
||||
- Republicação Lei22/2022 e Lei10/2025/BR248: reutilizados os exemplares acima; cotejo textual dos arts.15,17 e27, além da cadeia já analisada. Os sinais «[…]» preservam texto não alterado; não representam ausência de norma.
|
||||
|
||||
O exemplar de06/04/2017 localizado sob BR54 no inventário contém matéria aduaneira e não forneceu o Decreto8/2017 procurado. A coincidência de data não permite afirmar leitura dessa alteração do regulamento. Não foi localizado pelo nome/data, no inventário disponível, o texto do Decreto31/2012. Estes limites são de disponibilidade/correspondência nesta consulta, não afirmações de inexistência dos diplomas.
|
||||
|
||||
### Enquadramento documental confirmado e fronteira da conclusão
|
||||
|
||||
| Disposição exacta | Conteúdo confirmado por leitura | Aplicação ao percurso proposto |
|
||||
|---|---|---|
|
||||
| CIVA art.1(1)(a), PDF6, BR1426-(264) | Incidência sobre transmissões de bens e prestações de serviços onerosas no território nacional, nos termos do art.6, por sujeitos passivos nessa qualidade. | Não basta um montante: exigir natureza, onerosidade, localização e qualidade do sujeito. Importações do art.1(1)(b) ficam fora. |
|
||||
| CIVA art.2(1)(a), PDF6 | Pessoas residentes ou com estabelecimento/representação e exercício independente habitual de actividades enumeradas. | Contexto do fornecedor tem de ser identificado. Não automatizar elegibilidade fiscal a partir do perfil de investidor. |
|
||||
| CIVA arts.3(1),4(1), PDF6–7 | Transmissão geral: transferência onerosa de bens corpóreos; serviço: operação onerosa que não seja transmissão ou importação. | Classificar a operação; casos assimilados e especiais exigem enquadramento próprio e não entram implicitamente no lote. |
|
||||
| CIVA art.6(1), PDF7, BR1426-(265) | Bens situados em território nacional no início do transporte/expedição ou, sem estes, quando postos à disposição. | Condição documental para transmissão interna ordinária. Serviços têm art.6(3) e excepções nos números seguintes; não declarar que a localização do prestador resolve todos os serviços. |
|
||||
| CIVA art.15(1), PDF13, BR1426-(271) | Base geral é a contraprestação obtida ou a obter do adquirente, destinatário ou terceiro, sem prejuízo dos casos do n.º2. | Fundamenta base líquida tributável identificada, não qualquer receita prevista. Excluir do percurso automático as bases especiais do n.º2. |
|
||||
| CIVA art.15(5)(a)/(b), PDF14, BR1426-(272) | Inclui outros impostos/direitos/taxas, excepto o próprio IVA, e despesas acessórias debitadas, incluindo comissões, embalagem, transporte e seguros por conta do cliente. | Base sem IVA pode incluir mais que o preço nominal. Entrada deve indicar composição. |
|
||||
| CIVA art.15(6)(a)–(d), PDF14 | Exclui as indemnizações judiciais indicadas, descontos/abatimentos/bónus concedidos, pagamentos em nome/conta de terceiros registados adequadamente e embalagens retornáveis nas condições aí previstas. | Não excluir rubricas apenas pelo nome; conservar condições e evidência. O lote recebe base já determinada, sem construir um classificador destes casos. |
|
||||
| CIVA art.17(2), PDF14 | Taxa aplicável é a vigente quando o imposto se torna exigível. | A data relevante não é automaticamente a criação da simulação. Ligar o resolvedor à data de exigibilidade identificada. |
|
||||
| CIVA art.7(1)(a)/(b), PDF8, BR1426-(266) | Bens: colocação à disposição; serviços: realização ou cobrança/débito anterior nos termos do preceito. Números seguintes contêm situações próprias. | Não usar ano civil como substituto da data fiscal. Adiantamentos, prestações continuadas e outras excepções não são resolvidos por esta fórmula. |
|
||||
| CIVA art.27(5)(c)/(d), PDF17, BR1426-(275) | Distingue preço líquido e restantes elementos da base da taxa e montante do imposto. Lei10/2025 mantém essas alíneas por «[…]», alterando o n.º10. | Sustenta separação entre base e IVA no resultado. Não fixa nestas alíneas modo/escala de arredondamento. |
|
||||
| Regulamento, Decreto7/2008, arts.4 e5, PDF1–2 | Remete a matéria colectável para arts.15/16 do Código e o apuramento devido para arts.18–22. | IVA incidente da operação não equivale ao saldo a pagar após deduções. O lote não calcula declaração ou pagamento final. |
|
||||
|
||||
Período: Lei13/2016, art.4, PDF5, determina entrada em vigor em01/01/2017; taxa histórica de17% nesse exemplar não é usada para2026. A Lei22/2022 altera art.17(1) para16% desde01/01/2023 e adiciona art.15(10) para taxa reduzida, preservando os números gerais acima. A Lei10/2025, efectiva em01/01/2026, não lista art.15 ou17 entre os alterados. Esta cadeia suporta o percurso geral condicionado descrito; não certifica que todas as isenções, regimes e alterações regulamentares de2026 estejam consolidados. O intervalo de ensaio continua2026 e não cria expiração legal.
|
||||
|
||||
**Saldo do enquadramento:** fundamento geral da base e requisitos nucleares identificados. A elegibilidade de cada operação e as excepções não estão demonstradas por este facto. Para este lote manter entrada de base já apurada e contexto explícito, com sujeição geral comprovada ou declarada devidamente rotulada. Sem essa identificação, devolver indeterminação; não anunciar um classificador fiscal completo.
|
||||
|
||||
### Arredondamento: disposições encontradas que não podem ser generalizadas
|
||||
|
||||
| Disposição | Âmbito exacto | Conclusão |
|
||||
|---|---|---|
|
||||
| CIVA art.34, Lei10/2025, BR248/PDF14 | Extracção da base quando factura/registo já inclui imposto: dividir por116, multiplicar por100, arredondar para unidade mais próxima. | Não é arredondamento do IVA obtido de base líquida. Não transpor nem inferir empates. |
|
||||
| Regulamento art.9(1)(b), Decreto7/2008, BR16/PDF2, p.100 | Valor do meio de pagamento quando usado cheque ou vale de correio: arredondamento por excesso para meticais. | Texto confirmado nesse exemplar histórico. Não é regra de cálculo por linha/factura nem prova de política geral para pagamento electrónico; vigência consolidada específica não fechada. Não aplicar ao imposto incidente do lote. |
|
||||
|
||||
Não foi encontrado, nos dispositivos efectivamente consultados, fundamento suficiente para fixar escala, modo, tratamento de empates e agregação do IVA calculado sobre base líquida. Isto **não prova inexistência** de instrução aplicável. Não adoptar convenções de software, ofícios portugueses ou páginas de calculadoras como norma moçambicana.
|
||||
|
||||
A [comunicação oficial do Governo de30/09/2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/) anuncia alteração dos arts.3,7,8,15,16-A,18,21,23,28,36 e37 do regulamento, referindo Decreto7/2008 e alterações31/2012 e8/2017. A comunicação não fornece o articulado publicado nem a eficácia de cada disposição. Serve para identificar a dependência concreta de consolidação; não substitui o decreto publicado nem permite presumir que afecte ou resolva o arredondamento do lote.
|
||||
|
||||
**Questão concreta para esclarecimento documental:** qual o diploma, despacho, instrução/circular da AT ou disposição aplicável em2026 que determina, para IVA incidente sobre base líquida em factura, (a) número de casas/unidade, (b) modo e empates, (c) arredondamento por linha, por taxa agrupada ou por documento e (d) relação com os totais declarados? Obter o dispositivo e período, incluindo alterações pertinentes do regulamento e instruções de facturação. Não é um pedido para escolher uma convenção técnica nem para reconfirmar autenticidade oficial. Nenhuma mensagem foi enviada a terceiros.
|
||||
|
||||
### Exemplos documentais condicionados
|
||||
|
||||
Pressupostos explícitos para os exemplos: operação interna onerosa à taxa geral, sujeito no regime aplicável, sem isenção/redução/base especial, exigibilidade em2026 e valores MZN. São exemplos aritméticos de aplicação dos preceitos, não operações reais classificadas ou casos finais de aceitação.
|
||||
|
||||
| Exemplo | Base nos termos do art.15 | IVA exacto a16% | Estado |
|
||||
|---|---|---|---|
|
||||
| Preço100 + transporte debitado10 − desconto concedido5; sem outras rubricas | 105, se verificadas as condições dos n.os5 e6 | 16,8 | Intermédio exacto; não chamar saldo a pagar. |
|
||||
| Base já apurada100 | 100 | 16 | Exacto; não resolve política geral de arredondamento. |
|
||||
| Base já apurada0,03 | 0,03 | 0,0048 | Valor fiscal apresentado após arredondamento indeterminado. |
|
||||
| Duas linhas de base0,03 cada | 0,06 no total | 0,0048 por linha; 0,0096 no agregado | A convenção hipotética de arredondar ao cêntimo mais próximo produziria0,00 por linha e0,01 no agregado; diferença demonstra a dependência, não autoriza essa convenção. |
|
||||
|
||||
### Estimativa actualizada após a consulta
|
||||
|
||||
O enquadramento geral avançou; a dependência de arredondamento permanece concreta e a consolidação regulamentar pertinente não foi fechada. **Não é possível dar prazo incondicional de activação.** Estimativa restante de execução após disponibilização/esclarecimento desses elementos: **3–6h para concluir aplicação documental, ficha/casos e cadastro em rascunho QA; 12–20h para integração e aceitação técnica, total15–26h**. É reestimativa de engenharia, não horas medidas nem promessa de solução documental nesse intervalo. A espera por documento/esclarecimento e revisão/activação continuam excluídas. Se o fundamento introduzir agregação ou condições fora do contrato previsto, reestimar antes de ampliar o lote. Não houve implementação de código, cadastro ou activação nesta adenda.
|
||||
As15–26h anteriores eram condicionais e incluíam o trabalho de cadastro e integração que agora avançou em QA. O esclarecimento de uma política genérica de arredondamento deixa de integrar o caminho crítico deste lote. O saldo deve usar os resultados técnicos registados na adenda do manual existente, distinguindo trabalho já executado, revisão e qualquer consumidor futuro de pagamento/declaração. Não são horas medidas, promessa de activação nem estimativa do simulador fiscal integral. Publicação, activação e integração em `main` continuam separadas e não foram executadas.
|
||||
|
||||
@@ -347,5 +347,47 @@
|
||||
"rules_created": 0,
|
||||
"rules_activated": 0
|
||||
}
|
||||
},
|
||||
"fiscal_qa_v12_20261021": {
|
||||
"instruction": "Tempo: exact decimal IVA calculation without automatic rounding; presentation distinct; no activation or main integration",
|
||||
"supersedes": "v1.1 rounding-as-blocker and remaining estimate; no reopening accepted evidence",
|
||||
"status": "QA scoped implementation verified; fiscal product remains partial AUD-SIM-INT02",
|
||||
"proof": {
|
||||
"synthetic_qa": true,
|
||||
"database": "simulator_fiscal_delta_20261021_test",
|
||||
"rule_id": "01a15d4e-c69b-7264-bdee-1e56ee88bf94",
|
||||
"rule_status": "draft",
|
||||
"registration_reused": true,
|
||||
"simulation_id": "842e05f9-2b6e-4853-8ff5-81650633f6c4",
|
||||
"report_id": "49cac0bd-f49c-4613-a8f0-1ee5c285cf81",
|
||||
"exact_value": "0.0048",
|
||||
"rounding_applied": false,
|
||||
"report_sha256": "8ed8155a56fdf98ad26e2f01435644e062f27ca6b067ce3553415e974261c687",
|
||||
"result_report_equal": true,
|
||||
"previous_report_unchanged": true,
|
||||
"published_rules": 0,
|
||||
"source_reference_is_qa_fixture": true,
|
||||
"human_legal_approval": false
|
||||
},
|
||||
"tests": {
|
||||
"feature": 5,
|
||||
"feature_assertions": 51,
|
||||
"resolver_unit": 7,
|
||||
"resolver_assertions": 25,
|
||||
"frontend": 3,
|
||||
"typescript": "passed",
|
||||
"build": "passed",
|
||||
"pint": "passed"
|
||||
},
|
||||
"browser_observation": "Actual component SSR and native report HTML fiscal delta observed in Edge; not a newly saved native PDF or complete browser history flow",
|
||||
"limits": [
|
||||
"synthetic source reference and QA actors; not human legal approval",
|
||||
"QA PostgreSQL role bypasses RLS; application denials only",
|
||||
"no payment or return balance; scope declared",
|
||||
"no activation, migration, main merge or deployment"
|
||||
],
|
||||
"remaining_hours_scoped_qa_engineering": 0,
|
||||
"previous_15_26_hours": "Historical conditional estimate includes work now performed, not measured hours; review/activation and fiscal product scope separate",
|
||||
"record": "docs/investmoz/simulations/ficha-IVA-GERAL-BASE-LIQUIDA-v1.md"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1762,3 +1762,23 @@ A Tempo autorizou posteriormente a integração de `13f68f1b`. O commit foi inte
|
||||
A [ficha documental IVA-GERAL-BASE-LIQUIDA v1](../simulations/ficha-IVA-GERAL-BASE-LIQUIDA-v1.md) desenvolve separadamente a proposta `0fafd348`: fundamento da taxa e período, limites do art.34, exemplar digitalizado recuperado do inventário, parâmetros por cadastrar, lacunas de enquadramento/arredondamento, integração e critérios de aceitação. É rascunho documental sem cadastro ou activação. AUD-SIM-INT02 mantém-se parcial; os outros sete saldos aceites não são reabertos. Não foram repetidas provas aceites nem alterados cálculos, dados ou regras. A branch fiscal permanece separada de `main`.
|
||||
|
||||
**Adenda fiscal v1.1 — 21/10/2026:** a mesma ficha regista a leitura visual dirigida do CIVA/Lei13/2016, arts.1–4,6–7,15,17 e27, e a consulta do regulamento/Decreto7/2008. Enquadramento geral da base documentado, com composição, exclusões, localização e data de exigibilidade; não constitui classificação automática de operações. O arredondamento do art.34 refere-se à extracção de base com IVA incluído e o art.9(1)(b) do regulamento ao meio de pagamento cheque/vale; nenhum foi convertido em regra geral de arredondamento sobre base líquida. Permanecem por esclarecer precisão/modo/empates/agregação e a correspondência regulamentar pertinente para2026. A ficha identifica a questão documental concreta e exemplos aritméticos, sem inventar norma. Estimativa restante condicional: cadastro/aplicação documental3–6h + código/aceitação12–20h =15–26h, excluindo espera, revisão e activação. Fontes locais arquivadas com hash coincidente; não se atribui conferência ao utilizador. Nenhuma regra activada; AUD-SIM-INT02 continua parcial e a entrega13f68f1b mantém-se independente.
|
||||
|
||||
### Decisão posterior da Tempo e execução fiscal QA v1.2 — 21/10/2026
|
||||
|
||||
**Estado actual; substitui a condição bloqueante e a estimativa da adenda v1.1.** A Tempo determinou prosseguir neste lote sem arredondamento automático, preservando precisão decimal e distinguindo valor calculado de apresentação monetária. A ausência de uma política genérica de arredondamento deixa de bloquear o cálculo do IVA sobre base líquida. Isto não é regra universal para todos os impostos. Exigências de precisão de um formato de pagamento/declaração serão identificadas nesse consumidor, sem as generalizar. A ficha existente foi consolidada emv1.2; versões anteriores permanecem emGit. Não há questão adicional à Tempo como pré-condição deste cálculo nem validação jurídica atribuída ao utilizador.
|
||||
|
||||
**Cadastro:** artefacto `database/qa/iva-general-net-v1.json`; comando explícito `fiscal:qa-register-iva {actor} {source}`, com guarda da base QA isolada, papel existente de editor e vínculo a fonte cadastrada. Confere SHA256 do exemplar e usa a governação existente para criar apenas `draft`. Repetição conserva o mesmoID/versão; divergência não sobrescreve o cadastro. Não corre em deploy, importação ou migration. O clone persistido é `simulator_fiscal_delta_20261021_test`,127.0.0.1:55441. A referência de fonte e os actores usados no ensaio são fixtures sintéticas identificadas; não constituem revisão jurídica por pessoa real nem atribuição persistente de papéis organizacionais.
|
||||
|
||||
**Cálculo e proveniência:** `SimulationFiscalQaPreview` usa o resolvedor temporal e avaliador existentes, com percurso explícito de rascunho QA que não altera a elegibilidade da API de parâmetros publicados. O resultado guarda contexto declarado, fonte/versão/hash, regra/versão/DSL, passos racionais e decimal exacto; apresentação guarda valor e moeda separadamente, com `rounding_applied=false`. Não há float, truncagem, `toFixed` ou nó`round` neste percurso. Exemplos dirigidos:0,03×16%=0,0048;9007199254740993,0003×16%=1441151880758558,880048; zero explícito conserva0. Fonte/versão incompatível e fora da cobertura2026 continuam indeterminados, sem taxa substituta/zero. A data declarada de exigibilidade alimenta a resolução, sem expiração legal artificial em31/12/2026.
|
||||
|
||||
**Histórico e relatórios:** armazenamento existente de simulações, fontes de resultado e relatórios; sem schema novo. Entradas declaradas e proveniência da regra são fontes separadas. Snapshot/relatório não são reavaliados quando a regra é editada. Nova execução usa novo registo; a mesma chave e parâmetros reutiliza a execução. O ensaio persistido actual tem simulação`842e05f9-2b6e-4853-8ff5-81650633f6c4`, relatório`49cac0bd-f49c-4613-a8f0-1ee5c285cf81`, hash`8ed8155a56fdf98ad26e2f01435644e062f27ca6b067ce3553415e974261c687`; resultado/relatório iguais e relatório anterior conservado. Zero regras publicadas. Ecrã e linhas do dossier/CSV distinguem cálculo exacto QA, contexto declarado, estimativas do utilizador e saldo a pagar, que não é calculado neste lote.
|
||||
|
||||
**Verificação deste incremento:**5testes feature/51asserções;7testes do resolvedor/25asserções;3testes frontend;TypeScript,build ePint aprovados. Os testes feature correm com transacções no clone dedicado `simulator_fiscal_checks_20261021_test`, separado do cadastro persistido. A primeira tentativa de base vazia encontrou uma migration antiga que exige instalação nominal; não se contornou essa guarda nem se alterou o activo: usou-se cópia do schema QA existente e transacções, sem novas migrations. Cobertura inclui não publicação, indeterminação efectiva, idempotência, precisão fraccionária/grande, moeda obrigatória, recusa de nó de arredondamento, recusa foraQA, snapshot após edição, recuperação por nova autenticação API e recusa por outra conta.
|
||||
|
||||
Conferência visual dirigida no Edge do componente React real renderizado emSSR e do HTML produzido por `createProjectDossierPrintHtml` apenas para as novas linhas fiscais:0.0048, base0.03, taxa16, MZN e rótulos legíveis, sem corte visível. O invólucro de inspecção suprimiu apenas a abertura automática da janela de impressão numa cópia privada do HTML; não alterou o exportador. Não é prova de novo PDF nativo guardado nem novo percurso browser de histórico no formulário completo. API/histórico/relatório imutável e os modelos usados pelo ecrã/CSV/dossier têm provas dirigidas. A conta técnica QA ultrapassaRLS; recusas demonstram política da aplicação, não nova certificaçãoRLS. Provas aceites das10páginas, valores, CSV e recuperação/exportações de13f68f1b reutilizadas, sem repetição.
|
||||
|
||||
Provas privadas em`outputs/simulator-continuation-20261020/`: `fiscal-qa-receipt-v2.json`, `fiscal-qa-tests.json`, `fiscal-qa-build.log`, `fiscal-qa-snapshot.json`, `fiscal-component-review.html` e `fiscal-report-review.html`. Página/servidor temporários de inspecção encerrados. O recibo versionado desta continuação recebe a mesma reconciliação; a matriz existente recebe apenas adenda emAUD-SIM-INT02. Não se cria backlog paralelo.
|
||||
|
||||
**Saldo e estimativa:** o recorte agora autorizado — cadastro versionado, precisão/proveniência, resolução temporal QA, histórico e dados de relatório — está implementado e verificado nos limites acima;0h de engenharia identificada restante nesse recorte, revisão não incluída. As15–26h eram estimativa condicional anterior que incluía trabalho agora executado, não horas medidas nem estimativa ainda integralmente por consumir. AUD-SIM-INT02 permanece parcial para disponibilização fiscal de produto e cobertura integral; os outros sete itens não são reabertos. Pagamento, declaração, classificação automática e outros impostos exigem âmbito/estimativa próprios. Não declarar o simulador fiscal concluído.
|
||||
|
||||
**Integração:** commit desta candidata fica apenas na branch `codex/simulator-integrated-review`, descendente de0fafd348, sem push/merge emmain. A autorização anterior de integração de13f68f1b não é reutilizada para esta candidata fiscal. Após revisão consolidada, uma decisão específica de integração continua separada de cadastro/publicação de regras. Esta preparação não muda o destino, papéis activos ou dados activos. Não retirar a guarda QA ou mudar o ambiente do activo para contornar a separação; preparar o consumidor de produto e a sua disponibilização apenas no âmbito que vier a ser autorizado.
|
||||
|
||||
@@ -10652,7 +10652,7 @@ AUD-SEARCH-T10,Consistência transversal da pesquisa e navegação — Bibliotec
|
||||
AUD-SEARCH-T11,Consistência transversal da pesquisa e navegação — Legislação,Instruções expressas da Tempo na conversa de 20/10/2026; AGENTS.md; RF-MN-010 e AUD-GEO-01 preservados.,navegação/pesquisa,/legislacao,portal/services; portal/document — Reutiliza a correcção da biblioteca e preserva filtros jurídicos/documentais pertinentes.,Sem alteração remota nesta execução; revisão apenas na branch codex/opportunity-search.,HTTP/Inertia: resources.total=0; consumidor e query pública partilhados.,QA — delta delimitado; revisão Tempo pendente,"Situação jurídica, consulta e cálculo continuam independentes.",,"Filtros, contagem, resultados e URL coerentes; universos identificados; erro distinto de vazio; permissões e contexto conservados.",P1,,RF-MN-010; AUD-GEO-01,,,,,,,,,,,Mesmo padrão da biblioteca; não deve expor um universo concorrente.,"Situação jurídica, consulta e cálculo continuam independentes.",portal/services; portal/document; SearchState; SearchFeedback; TerritoryFields; portalNavigation,,"Integração em main apenas após revisão; assistência NLP, contratos e P7 separados.",Correcção/simplificação em QA,,HTTP/Inertia: resources.total=0; consumidor e query pública partilhados.,Pesquisa transversal QA 20/10/2026,20/10/2026,,,,,,,,,,"Provas comuns cobrem o componente reutilizado; HTTP vazio não demonstra operações de gestão ou publicação. Situação jurídica, consulta e cálculo continuam independentes.",,falha funcional; navegação; apresentação visual,,Disponível para revisão na aplicação QA local.
|
||||
AUD-GUIDE-CARD-20261020,"Related educational cards: image, source summary, clear actions",Tempo 20/10/2026; independent delta after 0e7d51f18fb4,educational presentation,/sectores/{slug}; /explorar/{group}; articles; /oportunidades and detail,EducationalArticleCard; EducationalGuides; LegacyContent; SectorInvestment,QA only,1 test / 8 assertions; PHP syntax; TypeScript/build; desktop2048/mobile390; keyboard,QA review pending,Normal QA sector and article verified against existing library resource 10 and local image 25; previous dossier proof retained,,Source-derived summary; public images; conditional opportunity action; context preserved,P1,,,,,,,,,,,,,,,,,,presentation,,,,20/10/2026,,,,,,,,,,,,content; navigation; visual presentation,,Ready for QA review
|
||||
AUD-SIM-INT01,Invalid one-off fiscal date can throw instead of returning validation state,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Fixed in QA; invalid-date checks; 38 directed frontend tests passed,Previous inline-date proof retained; unchanged delta not repeated,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,, | [Saldo consolidado21/10/2026] Concluído no recorte. Nenhum delta de validação de data por demonstrar; prova anterior preservada. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.,,20/10/2026,,,,,,,,,,,,,,
|
||||
AUD-SIM-INT02,Review actual fiscal parameters sources periods and eligibility,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Verification pending; no parameter activation,QA zero rules verified with full visibility; prepared archive and analyses reused; current homologation read awaits SSH authentication,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,," | [Leitura SSH 21/10/2026 08:17 Maputo] Zero regras em simulation_tax_rule_versions na homologação, incluindo todos os estados por leitura administrativa sem ocultação RLS. Seis referências em investment_legal_sources; não são parâmetros executáveis. QA também tinha zero regras. BEGIN READ ONLY/SELECT/ROLLBACK; zero escritas. Prova homologation-fiscal-readonly.json. O git HEAD registado é do checkout consultado, não certifica a revisão do build servido. A ausência de cadastro não é ausência de fontes oficiais nem reabre a aceitação da Tempo de 08/10. Não implementa a ligação funcional ao motor fiscal. | [Saldo consolidado21/10/2026] Parcial. Leitura remota concluída: zero regras de todos os estados na homologação, tal como QA; seis referências jurídicas não são parâmetros executáveis. Falta cadastro fundamentado por componente/âmbito/período e ligação funcional ao motor fiscal. Fontes e análises existentes reutilizáveis; aceitação oficial da Tempo de08/10 preservada. Não activar taxas nem importar/publicar por esta reconciliação. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação. | [Candidata21/10/2026] Diagnóstico fiscal separado no manual existente: fontes/analises já disponíveis; zero parâmetros executáveis na QA e homologação por leitura de todos os estados; governação/resolvedor temporal/DSL existentes; RunSimulation ainda aritmética personalizada sem consumir regras fiscais. Saldo: cadastro fundamentado, orquestração, contrato de resultado/histórico e ligação ao dossier/relatório, com casos por componente/período. Nenhuma regra criada nem activada; implementação fiscal integral aberta e excluída das1–2h de exportações. | [Proposta21/10/2026] Primeiro lote proposto: uma regra IVA geral sobre base liquida/MZN, cobertura demonstrada2026 sem inventar caducidade legal. Reutilizar fontes/analises/governacao/resolvedor/avaliador existentes; arredondamento e ambito aplicavel explicitamente por fundamentar antes de publicar. Cadastro4-8h e codigo/aceitacao12-20h, total16-28h condicionado ao acervo, separado das1-2h de exportacao. Criterios e exclusoes no manual existente. Nenhuma regra cadastrada/activada; nao e implementacao fiscal integral.",,20/10/2026,,,,,,,,,,,,,,
|
||||
AUD-SIM-INT02,Review actual fiscal parameters sources periods and eligibility,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Verification pending; no parameter activation,QA zero rules verified with full visibility; prepared archive and analyses reused; current homologation read awaits SSH authentication,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,," | [Leitura SSH 21/10/2026 08:17 Maputo] Zero regras em simulation_tax_rule_versions na homologação, incluindo todos os estados por leitura administrativa sem ocultação RLS. Seis referências em investment_legal_sources; não são parâmetros executáveis. QA também tinha zero regras. BEGIN READ ONLY/SELECT/ROLLBACK; zero escritas. Prova homologation-fiscal-readonly.json. O git HEAD registado é do checkout consultado, não certifica a revisão do build servido. A ausência de cadastro não é ausência de fontes oficiais nem reabre a aceitação da Tempo de 08/10. Não implementa a ligação funcional ao motor fiscal. | [Saldo consolidado21/10/2026] Parcial. Leitura remota concluída: zero regras de todos os estados na homologação, tal como QA; seis referências jurídicas não são parâmetros executáveis. Falta cadastro fundamentado por componente/âmbito/período e ligação funcional ao motor fiscal. Fontes e análises existentes reutilizáveis; aceitação oficial da Tempo de08/10 preservada. Não activar taxas nem importar/publicar por esta reconciliação. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação. | [Candidata21/10/2026] Diagnóstico fiscal separado no manual existente: fontes/analises já disponíveis; zero parâmetros executáveis na QA e homologação por leitura de todos os estados; governação/resolvedor temporal/DSL existentes; RunSimulation ainda aritmética personalizada sem consumir regras fiscais. Saldo: cadastro fundamentado, orquestração, contrato de resultado/histórico e ligação ao dossier/relatório, com casos por componente/período. Nenhuma regra criada nem activada; implementação fiscal integral aberta e excluída das1–2h de exportações. | [Proposta21/10/2026] Primeiro lote proposto: uma regra IVA geral sobre base liquida/MZN, cobertura demonstrada2026 sem inventar caducidade legal. Reutilizar fontes/analises/governacao/resolvedor/avaliador existentes; arredondamento e ambito aplicavel explicitamente por fundamentar antes de publicar. Cadastro4-8h e codigo/aceitacao12-20h, total16-28h condicionado ao acervo, separado das1-2h de exportacao. Criterios e exclusoes no manual existente. Nenhuma regra cadastrada/activada; nao e implementacao fiscal integral. | [QA IVA v1.2 21/10/2026] Orientacao posterior Tempo: calcular com precisao decimal exacta sem arredondamento automatico, separando apresentacao monetaria; arredondamento generico deixa de ser bloqueio deste lote. Cadastro draft versionado em clone QA; resolvedor/avaliador existentes; proveniencia, historico e relatorio imutavel ligados. 5 feature (51 assercoes), 7 unit (25), 3 frontend; types/build/Pint aprovados. Contexto declarado; nao e saldo a pagar. Zero regras publicadas; sem main/activacao. Recorte QA autorizado verificado (0h engenharia identificada restante); produto fiscal integral continua parcial. Limites e provas no mesmo manual e ficha v1.2; sem atribuicao de validacao juridica ao utilizador.",,20/10/2026,,,,,,,,,,,,,,
|
||||
AUD-SIM-INT03,Review isolation recovery across projects and sessions,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Existing controls and accepted proofs to reconcile; no leak established,Two dossiers and new login recovered in browser; cashflow persistence corrected and HTTP-tested; new visual cashflow delta pending,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P0,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,,"[Retoma 21/10/2026] QA 18576, clone simulator_review_delta_20261020_test: sessão sintética renovada por login normal; gravação autenticada e recarga recuperaram saldo 123, receita 900, custos 600, calendário mensal 10/vazio/0, crédito IVA estimado 500. Caixa conhecida 3713 MZN; total indeterminado desde mês 2; crédito estimado excluído da caixa. Prova visual cashflow-recovered-20261021.png e snapshot .txt em outputs/simulator-continuation-20261020. Não certifica recuperação destes novos valores após outro login, CSV/DOCX ou PDF nativo. Ligação de controlo do navegador perdida durante espera de download; não atribuir falha à aplicação. Leitura remota aguarda identificação da ligação SSH; zero alterações no destino. | [Saldo consolidado21/10/2026] Concluído no recorte. A/B, logout/novo login, recuperação e recusa pela aplicação demonstrados anteriormente. A tesouraria tem prova API de nova sessão e prova visual de recuperação por recarga. Não atribuir ao navegador nova sessão dos valores de tesouraria, nem nova certificação RLS; limites expressos conservados. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.",,20/10/2026,,,,,,,,,,,,,,
|
||||
AUD-SIM-INT04,Review stale and historical results and correct dossier,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Existing versions/fingerprints; targeted verification pending,Historical results visible after fix; 12 and 24 recovered; hashes unchanged; synthetic temporal resolver versions verified in memory only,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P1,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,," | [Saldo consolidado21/10/2026] Concluído no recorte demonstrado. Histórico24/12 visível, hashes preservados e versões temporais sintéticas do resolvedor conferidas. Execução fiscal integrada com parâmetros reais pertence ao saldo de02 e não foi demonstrada por estes testes. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.",,20/10/2026,,,,,,,,,,,,,,
|
||||
AUD-SIM-INT05,Independent financial controls unknown zero units and currencies,Tempo request 20/10/2026; integrated simulator review; same manual,simulator and shared language,,,QA only,Reconciliation pending; no invented tax assumptions,Independent business-plan and cashflow ledgers passed; complete credit recipe included in screen and exports,QA continuation verified; precise open inputs and cashflow visual check in same manual; 2-4 h remaining,,,P1,,,,,,,,,,,,,,,,,,diagnosis and targeted corrections,,"[Retoma 21/10/2026] QA 18576, clone simulator_review_delta_20261020_test: sessão sintética renovada por login normal; gravação autenticada e recarga recuperaram saldo 123, receita 900, custos 600, calendário mensal 10/vazio/0, crédito IVA estimado 500. Caixa conhecida 3713 MZN; total indeterminado desde mês 2; crédito estimado excluído da caixa. Prova visual cashflow-recovered-20261021.png e snapshot .txt em outputs/simulator-continuation-20261020. Não certifica recuperação destes novos valores após outro login, CSV/DOCX ou PDF nativo. Ligação de controlo do navegador perdida durante espera de download; não atribuir falha à aplicação. Leitura remota aguarda identificação da ligação SSH; zero alterações no destino. | [Saldo consolidado21/10/2026] Concluído nos controlos delimitados. Controlos independentes de caixa/plano/crédito, zero/desconhecido e moedas preservados. Não são validação de taxas legais nem certificação de toda a contabilidade. Ver saldo dos oito itens no manual existente; sem fecho global por esta reconciliação.",,20/10/2026,,,,,,,,,,,,,,
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -0,0 +1,24 @@
|
||||
import { fiscalQaRows } from "@/lib/fiscal-qa-preview";
|
||||
|
||||
export default function FiscalQaPreview({ snapshot }: { snapshot: unknown }) {
|
||||
const rows = fiscalQaRows(snapshot);
|
||||
if (rows.length === 0) return null;
|
||||
return (
|
||||
<section
|
||||
aria-label="Pré-visualização fiscal QA"
|
||||
className="my-3 rounded border border-amber-700 bg-amber-50 p-3"
|
||||
>
|
||||
<h5 className="font-semibold">
|
||||
Pré-visualização fiscal QA — cálculo exacto sem arredondamento
|
||||
</h5>
|
||||
<dl className="mt-2 space-y-2 text-sm">
|
||||
{rows.map(([label, value]) => (
|
||||
<div key={label}>
|
||||
<dt className="font-medium">{label}</dt>
|
||||
<dd className="break-all">{value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
} from '@/components/public-document-content';
|
||||
import { DossierPrefill } from '@/components/dossier-prefill';
|
||||
import { createOperationKey } from '@/lib/operation-key';
|
||||
import FiscalQaPreview from './fiscal-qa-preview';
|
||||
import { displayValue } from '@/lib/display-value';
|
||||
import {
|
||||
useCallback,
|
||||
@@ -886,6 +887,7 @@ export function SimulationForm() {
|
||||
() => readVersionedArithmeticReportReference(result),
|
||||
[result],
|
||||
);
|
||||
const isFiscalQaPreview = arithmeticSnapshot?.scenario_kind === 'fiscal_rule_qa_preview';
|
||||
const versionedReportMissing =
|
||||
arithmeticResult.integrity === 'versioned' &&
|
||||
arithmeticReportReference === null;
|
||||
@@ -3705,7 +3707,7 @@ export function SimulationForm() {
|
||||
{(current.id === 'tax' || result) && (
|
||||
<div className="space-y-3 border-t pt-4">
|
||||
<h4 className="font-semibold">
|
||||
{current.id === 'tax'
|
||||
{isFiscalQaPreview ? 'Pré-visualização fiscal QA seleccionada' : current.id === 'tax'
|
||||
? 'Cenário personalizado de planeamento'
|
||||
: 'Resultado histórico seleccionado'}
|
||||
</h4>
|
||||
@@ -3769,7 +3771,7 @@ export function SimulationForm() {
|
||||
submetido.
|
||||
</p>
|
||||
)}
|
||||
{result.status === 'completed' && (
|
||||
{result.status === 'completed' && !isFiscalQaPreview && (
|
||||
<p>
|
||||
Resultado aritmético indicativo:{' '}
|
||||
<strong className="break-all">
|
||||
@@ -3806,9 +3808,10 @@ export function SimulationForm() {
|
||||
taxas legais, elegibilidade ou obrigação
|
||||
fiscal.
|
||||
</p>
|
||||
{isFiscalQaPreview && <FiscalQaPreview snapshot={result.result_snapshot} />}
|
||||
{result.status === 'completed' && (
|
||||
<div className="mt-3 space-y-2 border-t pt-3">
|
||||
{arithmeticResult.integrity !==
|
||||
{!isFiscalQaPreview && arithmeticResult.integrity !==
|
||||
'invalid' && (
|
||||
<details className="rounded border p-3">
|
||||
<summary className="cursor-pointer font-semibold focus-visible:outline-2">
|
||||
@@ -4179,8 +4182,7 @@ export function SimulationForm() {
|
||||
'não identificada',
|
||||
'não identificada',
|
||||
)}{' '}
|
||||
· resultado
|
||||
aritmético{' '}
|
||||
· {source.result_snapshot.scenario_kind === 'fiscal_rule_qa_preview' ? 'IVA calculado exacto em QA' : 'resultado aritmético'}{' '}
|
||||
{displayScalar(
|
||||
sourceResult.value ??
|
||||
(sourceResult.integrity ===
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { isApiSimulationResultSnapshotV1 } from "./simulation-contract.ts";
|
||||
|
||||
export function fiscalQaRows(value: unknown): Array<[string, string]> {
|
||||
if (!isApiSimulationResultSnapshotV1(value)) return [];
|
||||
const snapshot = value as unknown as Record<string, unknown>;
|
||||
if (snapshot.scenario_kind !== "fiscal_rule_qa_preview") return [];
|
||||
const provenance = snapshot.fiscal_provenance as
|
||||
Record<string, unknown> | undefined;
|
||||
const rule = provenance?.rule as Record<string, unknown> | undefined;
|
||||
const source = rule?.source as Record<string, unknown> | undefined;
|
||||
const parameters = rule?.parameters as
|
||||
Record<string, Record<string, unknown>> | undefined;
|
||||
const intermediate = snapshot.intermediate as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
const text = (item: unknown) =>
|
||||
typeof item === "string" || typeof item === "number"
|
||||
? String(item)
|
||||
: "não disponível";
|
||||
return [
|
||||
["Relatório", "Pré-visualização fiscal QA de rascunho — sem activação"],
|
||||
[
|
||||
"IVA calculado (exacto; não é saldo a pagar)",
|
||||
value.result.value ?? "Indeterminado",
|
||||
],
|
||||
["Explicação", value.result.explanation],
|
||||
["Base tributável declarada", text(snapshot.base)],
|
||||
["Taxa cadastrada (%)", text(parameters?.IVA_GENERAL_RATE?.value)],
|
||||
["Moeda explícita", text(value.context.currency)],
|
||||
["Data de exigibilidade declarada", value.context.reference_date],
|
||||
[
|
||||
"Enquadramento",
|
||||
"Declarado pelo utilizador; não validado juridicamente",
|
||||
],
|
||||
[
|
||||
"Regra / versão / estado",
|
||||
`${text(rule?.rule_code)} / ${text(rule?.rule_version)} / rascunho`,
|
||||
],
|
||||
[
|
||||
"Vigência cadastrada",
|
||||
`${text(rule?.valid_from)} até ${rule?.valid_until === null ? "sem termo indicado" : text(rule?.valid_until)}`,
|
||||
],
|
||||
[
|
||||
"Fonte da taxa",
|
||||
`${text(source?.diploma)} — ${text(source?.article)}`,
|
||||
],
|
||||
["Hash do exemplar da taxa", text(rule?.source_sha256)],
|
||||
["Representação racional exacta", text(intermediate?.result)],
|
||||
[
|
||||
"Arredondamento",
|
||||
"Não aplicado. Valor calculado preservado sem truncar ou arredondar; apresentação monetária separada.",
|
||||
],
|
||||
[
|
||||
"Limitações",
|
||||
"Regra não publicada; cobertura demonstrada em 2026; sem deduções, declaração ou saldo a pagar.",
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { displayValue } from './display-value.ts';
|
||||
import { fiscalQaRows } from './fiscal-qa-preview.ts';
|
||||
import {
|
||||
buildProjectDossierRows,
|
||||
type ProjectDossierRow,
|
||||
@@ -344,6 +345,11 @@ function makeSections(
|
||||
}
|
||||
|
||||
const finance = sections.get('finance');
|
||||
if (finance && currentCalculation?.snapshot?.scenario_kind === 'fiscal_rule_qa_preview') {
|
||||
for (const [field, value] of fiscalQaRows(currentCalculation.snapshot)) {
|
||||
finance.rows.push({ field, value, origin: 'Snapshot fiscal QA preservado; sem recálculo', classification: 'Rascunho QA; cálculo exacto sem arredondamento automático; não é saldo a pagar' });
|
||||
}
|
||||
}
|
||||
if (finance && currentCalculation) {
|
||||
const snapshot = currentCalculation.snapshot;
|
||||
const result =
|
||||
@@ -417,10 +423,12 @@ function makeSections(
|
||||
{
|
||||
field: 'Fórmula e valores usados',
|
||||
value:
|
||||
snapshot && typeof snapshot.formula_version === 'string'
|
||||
snapshot?.scenario_kind === 'fiscal_rule_qa_preview'
|
||||
? `Base tributável declarada × taxa cadastrada ÷ 100; cálculo decimal exacto, sem arredondamento. Ver regra e proveniência QA acima.`
|
||||
: snapshot && typeof snapshot.formula_version === 'string'
|
||||
? `Fórmula ${snapshot.formula_version}; base ${displayValue(snapshot.base, 'não disponível')}; taxa ${displayValue(snapshot.rate_percent, 'não disponível')}%; redução ${displayValue(snapshot.reduction_percent, 'não disponível')}%; moeda ${displayValue(snapshot.currency, 'não indicada')}`
|
||||
: 'A fórmula não foi identificada no snapshot; ver explicação da execução',
|
||||
origin: 'Snapshot guardado; sem aplicação de regra legal',
|
||||
origin: snapshot?.scenario_kind === 'fiscal_rule_qa_preview' ? 'Snapshot QA de regra em rascunho; sem activação ou validação jurídica da operação' : 'Snapshot guardado; sem aplicação de regra legal',
|
||||
classification: 'Reutilização do resultado existente',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { csvCell } from './csv.js';
|
||||
import { fiscalQaRows } from './fiscal-qa-preview.ts';
|
||||
import { isApiSimulationResultSnapshotV1 } from './simulation-contract.ts';
|
||||
|
||||
export type SimulationDraft = Record<string, string | string[]>;
|
||||
@@ -1848,6 +1849,13 @@ export function exportCompletedArithmeticCsv(
|
||||
);
|
||||
}
|
||||
const result = simulation.result_snapshot as Record<string, unknown>;
|
||||
if (result.scenario_kind === 'fiscal_rule_qa_preview') {
|
||||
const rows = fiscalQaRows(result);
|
||||
const report = readVersionedArithmeticReportReference(simulation);
|
||||
if (!rows.length || !report) throw new Error('A pré-visualização fiscal não corresponde ao relatório preservado.');
|
||||
return `\uFEFF${[...rows, ['ID do relatório', report.id], ['Hash do relatório', report.report_sha256], ['Data de exportação', exportedAt]]
|
||||
.map((field) => field.map(csvCell).join(',')).join('\r\n')}`;
|
||||
}
|
||||
const decimal = (value: unknown, label: string) => {
|
||||
if (typeof value !== 'string' || !/^(?:\d+)(?:\.\d+)?$/.test(value))
|
||||
throw new Error(
|
||||
|
||||
@@ -27,6 +27,7 @@ Route::middleware([CorrelationId::class, 'auth', 'verified', \App\Http\Middlewar
|
||||
Route::post('/api/v2/investment/admin/tax-rules', [SimulationTaxRuleGovernanceController::class, 'store']);
|
||||
Route::get('/api/v2/investment/admin/tax-rules/{id}', [SimulationTaxRuleGovernanceController::class, 'show'])->whereUuid('id');
|
||||
Route::put('/api/v2/investment/admin/tax-rules/{id}', [SimulationTaxRuleGovernanceController::class, 'update'])->whereUuid('id');
|
||||
Route::post('/api/v2/investment/admin/tax-rules/{id}/qa-preview', [SimulationTaxRuleGovernanceController::class, 'qaPreview'])->whereUuid('id');
|
||||
Route::post('/api/v2/investment/admin/tax-rules/{id}/{action}', [SimulationTaxRuleGovernanceController::class, 'transition'])
|
||||
->whereUuid('id')->whereIn('action', ['submit', 'review', 'reject', 'approve', 'publish', 'suspend', 'archive']);
|
||||
Route::get('/management/investment/zones/operational-install',function(\Illuminate\Http\Request $r,\App\Services\InvestmentZoneOperationalDetailsInstallation $s){return response()->json(['plan'=>$s->preview($r->user())]);});
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Jobs\RunSimulation;
|
||||
use App\Models\InvestmentLegalSource;
|
||||
use App\Models\Simulation;
|
||||
use App\Models\SimulationTaxRuleVersion;
|
||||
use App\Models\User;
|
||||
use App\Services\SimulationExecution;
|
||||
use App\Services\SimulationTaxParameterResolver;
|
||||
use App\Services\SimulationTaxRuleGovernance;
|
||||
use Brick\Math\BigRational;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SimulationFiscalQaPreviewTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
private User $editor;
|
||||
|
||||
private InvestmentLegalSource $source;
|
||||
|
||||
private string $ruleId;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Queue::fake();
|
||||
$this->editor = User::factory()->withTwoFactor()->create();
|
||||
config(['portal.managers' => [$this->editor->email], 'simulation-tax-governance.editors' => [$this->editor->email],
|
||||
'simulation-tax-governance.publishers' => [$this->editor->email]]);
|
||||
$this->source = InvestmentLegalSource::create([
|
||||
'title' => 'QA fixture: Lei22/2022 republicada; sem activação',
|
||||
'url' => 'https://www.inm.gov.mz/', 'diploma' => 'Lei 22/2022', 'article' => 'Artigo 17(1)',
|
||||
'competent_entity' => 'Assembleia da República', 'valid_from' => '2023-01-01', 'valid_until' => null,
|
||||
'validation_status' => 'validated', 'owner_id' => $this->editor->id, 'version_no' => 1,
|
||||
'validator_id' => User::factory()->create()->id, 'validated_at' => now(),
|
||||
]);
|
||||
$this->artisan('fiscal:qa-register-iva', ['actor' => $this->editor->id, 'source' => $this->source->id])->assertSuccessful();
|
||||
$this->ruleId = SimulationTaxRuleVersion::where('rule_code', 'IVA-GERAL-BASE-LIQUIDA')->value('id');
|
||||
}
|
||||
|
||||
private function preview(array $overrides = [], ?string $key = null)
|
||||
{
|
||||
return $this->actingAs($this->editor)->postJson('/api/v2/investment/admin/tax-rules/'.$this->ruleId.'/qa-preview', [
|
||||
'base' => '0.03', 'currency' => 'MZN', 'reference_date' => '2026-10-21', 'operation' => 'internal_general_net', ...$overrides,
|
||||
], ['Idempotency-Key' => $key ?? (string) Str::uuid()]);
|
||||
}
|
||||
|
||||
private function finish(string $id): array
|
||||
{
|
||||
(new RunSimulation($id, $this->editor->id, null))->handle(app(SimulationExecution::class));
|
||||
|
||||
return Simulation::findOrFail($id)->result_snapshot;
|
||||
}
|
||||
|
||||
public function test_draft_registration_reuses_version_and_never_exposes_a_published_parameter(): void
|
||||
{
|
||||
$this->artisan('fiscal:qa-register-iva', ['actor' => $this->editor->id, 'source' => $this->source->id])->assertSuccessful();
|
||||
$this->assertSame(1, SimulationTaxRuleVersion::where('rule_code', 'IVA-GERAL-BASE-LIQUIDA')->count());
|
||||
$this->assertDatabaseHas('simulation_tax_rule_versions', ['id' => $this->ruleId, 'status' => 'draft']);
|
||||
$resolved = app(SimulationTaxParameterResolver::class)->resolvePublishedForPeriod('IVA', '2026-10-21', '2026-10-21',
|
||||
['operation' => 'internal_general_net', 'currency' => 'MZN']);
|
||||
$this->assertSame([], $resolved);
|
||||
$this->actingAs($this->editor)->postJson('/api/v2/investment/admin/tax-rules/'.$this->ruleId.'/publish', ['version_no' => 1, 'evidence' => 'QA attempt must not publish an incomplete fiscal rule.'])->assertUnprocessable();
|
||||
}
|
||||
|
||||
public function test_fractional_result_is_exact_and_snapshot_survives_rule_edits_and_new_session(): void
|
||||
{
|
||||
$key = (string) Str::uuid();
|
||||
$id = $this->preview([], $key)->assertAccepted()->json('data.id');
|
||||
$this->preview([], $key)->assertOk()->assertJsonPath('replayed', true)->assertJsonPath('data.id', $id);
|
||||
$this->preview(['base' => '1'], $key)->assertConflict();
|
||||
$snapshot = $this->finish($id);
|
||||
$this->assertSame('0.0048', $snapshot['result']['value']);
|
||||
$this->assertSame('calculated', $snapshot['result']['status']);
|
||||
$this->assertSame('0.0048', $snapshot['presentation']['value']);
|
||||
$this->assertFalse($snapshot['presentation']['rounding_applied']);
|
||||
$this->assertEquals(0, BigRational::of($snapshot['intermediate']['result'])->compareTo('0.0048'));
|
||||
$this->assertSame('draft', $snapshot['fiscal_provenance']['rule']['status']);
|
||||
$this->assertSame('user_declared', $snapshot['fiscal_provenance']['scope_status']);
|
||||
$this->assertSame([], $snapshot['result']['missing_inputs']);
|
||||
$rule = SimulationTaxRuleVersion::findOrFail($this->ruleId);
|
||||
$params = $rule->parameters;
|
||||
$params['IVA_GENERAL_RATE']['value'] = '17';
|
||||
app(SimulationTaxRuleGovernance::class)->update($this->editor, $this->ruleId, 1, [
|
||||
'rule_code' => $rule->rule_code, 'rule_version' => 1, 'component' => 'IVA', 'scope_key' => $rule->scope_key,
|
||||
'valid_from' => '2023-01-01', 'valid_until' => null, 'legal_source_id' => $this->source->id,
|
||||
'source_sha256' => $rule->source_sha256, 'rule_definition' => $rule->rule_definition, 'parameters' => $params,
|
||||
'rounding_policy' => $rule->rounding_policy,
|
||||
]);
|
||||
auth()->logout();
|
||||
$this->actingAs(User::findOrFail($this->editor->id))->getJson('/api/v1/simulations/'.$id)->assertOk()
|
||||
->assertJsonPath('data.result_snapshot.fiscal_provenance.rule.parameters.IVA_GENERAL_RATE.value', '16');
|
||||
$reportId = DB::table('simulation_reports')->where('simulation_id', $id)->value('id');
|
||||
$this->getJson('/api/v1/simulations/'.$id.'/reports/'.$reportId)->assertOk()->assertJsonPath('data.report_snapshot', $snapshot);
|
||||
$this->getJson('/api/v1/simulations/history')->assertOk()->assertJsonPath('meta.total', 1);
|
||||
$this->assertDatabaseHas('simulation_result_sources', ['source_type' => 'tax_rule_draft', 'source_id' => $this->ruleId]);
|
||||
$resultId = DB::table('simulation_results')->where('simulation_id', $id)->value('id');
|
||||
$declared = json_decode(DB::table('simulation_result_sources')->where('result_id', $resultId)->where('source_type', 'user_input')->value('source_snapshot'), true);
|
||||
$this->assertSame('0.03', $declared['base']);
|
||||
$this->assertArrayNotHasKey('fiscal_preview', $declared);
|
||||
$other = User::factory()->create(['email_verified_at' => now()]);
|
||||
$this->actingAs($other)->getJson('/api/v1/simulations/'.$id)->assertNotFound();
|
||||
$this->getJson('/api/v1/simulations/'.$id.'/reports/'.$reportId)->assertNotFound();
|
||||
$this->getJson('/api/v1/simulations/history')->assertOk()->assertJsonPath('meta.total', 0);
|
||||
}
|
||||
|
||||
public function test_coverage_and_source_version_mismatch_stay_indeterminate_without_an_intermediate(): void
|
||||
{
|
||||
$id = $this->preview(['reference_date' => '2025-12-31'])->assertAccepted()->json('data.id');
|
||||
$snapshot = $this->finish($id);
|
||||
$this->assertContains('demonstrated_coverage', $snapshot['result']['missing_inputs']);
|
||||
$this->assertNull($snapshot['intermediate']);
|
||||
$this->source->update(['version_no' => 2]);
|
||||
$id = $this->preview()->assertAccepted()->json('data.id');
|
||||
$snapshot = $this->finish($id);
|
||||
$this->assertContains('eligible_parameter', $snapshot['result']['missing_inputs']);
|
||||
$this->assertNull($snapshot['intermediate']);
|
||||
}
|
||||
|
||||
public function test_zero_is_an_explicit_input_missing_currency_is_rejected_and_non_qa_preview_is_unavailable(): void
|
||||
{
|
||||
$id = $this->preview(['base' => '0'])->assertAccepted()->json('data.id');
|
||||
$snapshot = $this->finish($id);
|
||||
$this->assertEquals(0, BigRational::of($snapshot['intermediate']['result'])->compareTo(0));
|
||||
$this->assertSame('0', $snapshot['result']['value']);
|
||||
$largeId = $this->preview(['base' => '9007199254740993.0003'])->assertAccepted()->json('data.id');
|
||||
$this->assertSame('1441151880758558.880048', $this->finish($largeId)['result']['value']);
|
||||
$this->preview(['currency' => null])->assertUnprocessable();
|
||||
config(['database.connections.pgsql.url' => 'not-an-isolated-qa-target']);
|
||||
$this->preview()->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_this_consumer_refuses_a_round_node_instead_of_silently_applying_it(): void
|
||||
{
|
||||
$rule = SimulationTaxRuleVersion::findOrFail($this->ruleId);
|
||||
$definition = $rule->rule_definition;
|
||||
$definition['nodes']['rounded'] = ['op' => 'round', 'args' => ['intermediate'], 'scale' => 2, 'mode' => 'HALF_UP'];
|
||||
$definition['result'] = 'rounded';
|
||||
// Deliberately altered QA draft; no rule is published by this test.
|
||||
$rule->update(['rule_definition' => $definition]);
|
||||
$this->preview()->assertUnprocessable()->assertJsonPath('message', 'Este percurso preserva o valor exacto e não aplica arredondamento automático.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { fiscalQaRows } from "../../resources/js/lib/fiscal-qa-preview.ts";
|
||||
import { exportCompletedArithmeticCsv } from "../../resources/js/lib/simulation-form.ts";
|
||||
import { createProjectDossierReport } from "../../resources/js/lib/project-dossier-report.ts";
|
||||
|
||||
function fixture() {
|
||||
const snapshot = {
|
||||
contract_version: 1,
|
||||
scenario_kind: "fiscal_rule_qa_preview",
|
||||
base: "0.03",
|
||||
currency: "MZN",
|
||||
indicative_total: "0.0048",
|
||||
legal_interpretation: false,
|
||||
formula_version: "investmoz.tax.v1",
|
||||
context: {
|
||||
reference_date: "2026-10-21",
|
||||
currency: "MZN",
|
||||
currency_fraction_digits: 2,
|
||||
scenario_id: "61000100-0000-4000-8000-000000000099",
|
||||
scenario_version: 1,
|
||||
engine_version: "fiscal-draft-preview-v1",
|
||||
ruleset_version: "IVA-GERAL-BASE-LIQUIDA:1",
|
||||
},
|
||||
result: {
|
||||
status: "calculated",
|
||||
value: "0.0048",
|
||||
unit: "MZN",
|
||||
explanation:
|
||||
"Cálculo exacto QA; enquadramento declarado; não é saldo a pagar.",
|
||||
missing_inputs: [],
|
||||
source_ids: ["source-fixture"],
|
||||
},
|
||||
intermediate: { result: "48/10000" },
|
||||
fiscal_provenance: {
|
||||
rule: {
|
||||
rule_code: "IVA-GERAL-BASE-LIQUIDA",
|
||||
rule_version: 1,
|
||||
valid_from: "2023-01-01",
|
||||
valid_until: null,
|
||||
source_sha256: "a".repeat(64),
|
||||
source: { diploma: "Lei 22/2022", article: "Artigo 17(1)" },
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
id: "61000100-0000-4000-8000-000000000088",
|
||||
status: "completed",
|
||||
version_no: 2,
|
||||
result_snapshot: snapshot,
|
||||
reports: [
|
||||
{
|
||||
id: "61000100-0000-4000-8000-000000000077",
|
||||
version_no: 1,
|
||||
status: "ready",
|
||||
report_snapshot: structuredClone(snapshot),
|
||||
report_sha256: "b".repeat(64),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
test("screen rows preserve sub-cent IVA as a string and distinguish calculation from payment", () => {
|
||||
const simulation = fixture();
|
||||
const before = JSON.stringify(simulation);
|
||||
const rows = fiscalQaRows(simulation.result_snapshot);
|
||||
assert.equal(
|
||||
rows.find(([label]) => label.startsWith("IVA calculado"))[1],
|
||||
"0.0048",
|
||||
);
|
||||
assert.match(
|
||||
rows.find(([label]) => label === "Arredondamento")[1],
|
||||
/Não aplicado/,
|
||||
);
|
||||
assert.match(
|
||||
rows.find(([label]) => label === "Enquadramento")[1],
|
||||
/não validado juridicamente/,
|
||||
);
|
||||
assert.equal(JSON.stringify(simulation), before);
|
||||
});
|
||||
|
||||
test("CSV preserves the exact amount and refuses a mismatched immutable report", () => {
|
||||
const simulation = fixture();
|
||||
const csv = exportCompletedArithmeticCsv(
|
||||
simulation,
|
||||
"2026-10-21T09:00:00Z",
|
||||
);
|
||||
assert.match(csv, /0\.0048/);
|
||||
assert.doesNotMatch(csv, /Resultado fiscal final,Indeterminado/);
|
||||
simulation.reports[0].report_snapshot.result.value = "0.00";
|
||||
assert.throws(
|
||||
() => exportCompletedArithmeticCsv(simulation),
|
||||
/relatório preservado/,
|
||||
);
|
||||
});
|
||||
|
||||
test("dossier report uses the same exact snapshot and preserves genuine indetermination", () => {
|
||||
const simulation = fixture();
|
||||
function report() {
|
||||
return createProjectDossierReport(
|
||||
{ project_title: "QA fiscal", currency: "MZN" },
|
||||
{
|
||||
selectedSections: new Set(["finance"]),
|
||||
currentCalculation: {
|
||||
status: "completed",
|
||||
version: 2,
|
||||
snapshot: simulation.result_snapshot,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
const rows = report().sections.find(
|
||||
(section) => section.id === "finance",
|
||||
).rows;
|
||||
assert.equal(
|
||||
rows.find((row) => row.field.startsWith("IVA calculado")).value,
|
||||
"0.0048",
|
||||
);
|
||||
assert.equal(
|
||||
rows.find((row) => row.field === "Fonte da taxa").value,
|
||||
"Lei 22/2022 — Artigo 17(1)",
|
||||
);
|
||||
simulation.result_snapshot.result.status = "indeterminate";
|
||||
simulation.result_snapshot.result.value = null;
|
||||
simulation.result_snapshot.indicative_total = null;
|
||||
simulation.result_snapshot.result.missing_inputs = ["eligible_parameter"];
|
||||
assert.equal(
|
||||
report()
|
||||
.sections.find((section) => section.id === "finance")
|
||||
.rows.find((row) => row.field.startsWith("IVA calculado")).value,
|
||||
"Indeterminado",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user