Integrate monthly IVA with projected sales in QA

This commit is contained in:
2026-10-21 14:05:46 +02:00
parent 0d7b70459d
commit 3f5430c3ce
18 changed files with 690 additions and 12 deletions
@@ -76,6 +76,75 @@ class SimulationPublishedFiscalTest extends TestCase
return Simulation::with('reports')->findOrFail($id);
}
private function monthlyInput(array $override = []): array
{
return ['scenario_kind' => 'fiscal_rule_published', 'currency' => 'MZN', 'scope_declared' => true, 'sales_vat_basis' => 'net',
'monthly_sales' => [['date' => '2026-01-01', 'amount' => '1000.03'], ['date' => '2026-02-01', 'amount' => '0'], ['date' => '2026-03-01', 'amount' => null]], ...$override];
}
public function test_monthly_sales_preserve_zero_missing_values_recovery_and_report_with_no_double_cash_effect(): void
{
$key = (string) Str::uuid();
$input = $this->monthlyInput();
$id = $this->actingAs($this->consumer)->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => $key])->assertAccepted()->json('data.id');
$snapshot = $this->finish($id)->result_snapshot;
$rows = $snapshot['monthly_projection']['rows'];
self::assertSame('160.0048', $rows[0]['vat']);
self::assertSame('1160.0348', $rows[0]['gross_sales']);
self::assertSame('16', $rows[0]['rate']);
self::assertSame('0', $rows[1]['vat']);
self::assertSame('0', $rows[1]['gross_sales']);
self::assertNull($rows[2]['vat']);
self::assertNull($rows[2]['net_sales']);
self::assertSame('missing_sales', $rows[2]['status']);
self::assertNull($snapshot['result']['value']);
self::assertStringContainsString('nem o IVA é deduzido como custo', $snapshot['monthly_projection']['cash_treatment']);
$this->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => $key])->assertOk()->assertJsonPath('data.id', $id);
$this->getJson('/api/v1/simulations/'.$id)->assertOk()->assertJsonPath('data.result_snapshot', $snapshot);
$report = Simulation::with('reports')->findOrFail($id)->reports->first();
$this->getJson('/api/v1/simulations/'.$id.'/reports/'.$report->id)->assertOk()->assertJsonPath('data.report_snapshot', $snapshot);
$input['monthly_sales'][0]['amount'] = '2000';
$input['monthly_sales'][2]['amount'] = '3000';
$newId = $this->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => (string) Str::uuid()])->assertAccepted()->json('data.id');
$new = $this->finish($newId)->result_snapshot;
self::assertSame('320', $new['monthly_projection']['rows'][0]['vat']);
self::assertSame('480', $new['monthly_projection']['rows'][2]['vat']);
self::assertSame('800', $new['result']['value']);
self::assertSame($snapshot, Simulation::findOrFail($id)->result_snapshot);
self::assertSame(2, Simulation::where('owner_user_id', $this->consumer->id)->count());
}
public function test_monthly_gross_unknown_scope_and_server_rate_protection_do_not_invent_net_sales(): void
{
foreach (['included', 'unknown'] as $basis) {
$id = $this->actingAs($this->consumer)->postJson('/api/v1/simulations', $this->monthlyInput(['sales_vat_basis' => $basis]), ['Idempotency-Key' => (string) Str::uuid()])->assertAccepted()->json('data.id');
$row = $this->finish($id)->result_snapshot['monthly_projection']['rows'][0];
self::assertSame('1000.03', $row['sales_declared']);
self::assertSame('unsupported_basis', $row['status']);
self::assertNull($row['net_sales']);
self::assertNull($row['vat']);
self::assertNull($row['rate']);
}
$id = $this->postJson('/api/v1/simulations', $this->monthlyInput(['scope_declared' => false]), ['Idempotency-Key' => (string) Str::uuid()])->assertAccepted()->json('data.id');
self::assertNull($this->finish($id)->result_snapshot['monthly_projection']['rows'][0]['vat']);
$this->postJson('/api/v1/simulations', $this->monthlyInput(['rate' => '17']), ['Idempotency-Key' => (string) Str::uuid()])->assertUnprocessable();
$this->postJson('/api/v1/simulations', $this->monthlyInput(['monthly_sales' => [['date' => '2027-01-01', 'amount' => '100']]]), ['Idempotency-Key' => (string) Str::uuid()])->assertUnprocessable();
}
public function test_monthly_frozen_admission_survives_suspension_and_does_not_duplicate_on_retry(): void
{
$key = (string) Str::uuid();
$input = $this->monthlyInput();
$id = $this->actingAs($this->consumer)->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => $key])->assertAccepted()->json('data.id');
app(SimulationTaxRuleGovernance::class)->transition($this->publisher, $this->rule->id, $this->rule->version_no, 'suspend', 'QA: suspend monthly consumer after admission, preserve frozen version.');
$this->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => (string) Str::uuid()])->assertStatus(503);
$this->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => $key])->assertOk()->assertJsonPath('data.id', $id);
$snapshot = $this->finish($id)->result_snapshot;
self::assertSame('160.0048', $snapshot['monthly_projection']['rows'][0]['vat']);
self::assertSame($this->rule->id, $snapshot['monthly_projection']['rows'][0]['rule_id']);
self::assertSame('published', $snapshot['fiscal_provenance']['rule']['status']);
}
public function test_iva_query_diagnostics_explain_the_reported_contract_gap_without_calculating_or_changing_answers(): void
{
$count = Simulation::count();