CluPilotCloud/tests/Feature/Billing/FinanceSettingsTest.php

135 lines
4.9 KiB
PHP

<?php
use App\Livewire\Admin\EditInvoiceSeries;
use App\Livewire\Admin\Finance;
use App\Models\Invoice;
use App\Models\InvoiceSeries;
use App\Support\CompanyProfile;
use Livewire\Livewire;
/**
* The details that end up on a legal document, and the parts of an invoice
* series that stop being editable once one has been issued.
*/
it('refuses to consider itself ready without the fields an invoice legally needs', function () {
// An invoice without a name, an address or a VAT number is not a valid
// invoice in Austria. Issuing one is worse than refusing to.
expect(CompanyProfile::missingForInvoicing())
->toContain('name')->toContain('address')->toContain('vat_id');
CompanyProfile::put([
'name' => 'CluPilot Cloud e.U.',
'address' => 'Musterstraße 1',
'postcode' => '1010',
'city' => 'Wien',
'vat_id' => 'ATU12345678',
]);
expect(CompanyProfile::missingForInvoicing())->toBe([]);
});
it('stores the company details from the finance tab', function () {
Livewire::actingAs(operator('Owner'), 'operator')
->test(Finance::class)
->set('company.name', 'CluPilot Cloud e.U.')
->set('company.address', 'Musterstraße 1')
->set('company.postcode', '1010')
->set('company.city', 'Wien')
->set('company.country', 'Österreich')
->set('company.vat_id', 'ATU12345678')
->set('company.payment_days', 14)
->set('taxRate', 20)
->call('saveCompany')
->assertHasNoErrors();
expect(CompanyProfile::get('vat_id'))->toBe('ATU12345678')
->and(CompanyProfile::taxRate())->toBe(20.0);
});
it('drops a field that is not part of the profile', function () {
// This is fed from a form. A forged key must not be able to write an
// arbitrary setting into the same table the site-visibility switch uses.
CompanyProfile::put(['site.public' => false, 'name' => 'Echt']);
expect(App\Support\Settings::get('site.public', true))->toBeTrue()
->and(CompanyProfile::get('name'))->toBe('Echt');
});
it('lets a series be renamed and its prefix corrected while nothing has been issued', function () {
$series = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail();
Livewire::actingAs(operator('Owner'), 'operator')
->test(EditInvoiceSeries::class, ['uuid' => $series->uuid])
->set('name', 'Ausgangsrechnung')
->set('prefix', 'ar')
->call('save')
->assertHasNoErrors();
expect($series->refresh()->prefix)->toBe('AR')
->and($series->name)->toBe('Ausgangsrechnung');
});
it('freezes the prefix once a document carries it', function () {
// The prefix is part of every number already issued. Changing it renames
// nothing and leaves a series whose past says RE and whose future says AR.
$series = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail();
issueOne($series);
Livewire::actingAs(operator('Owner'), 'operator')
->test(EditInvoiceSeries::class, ['uuid' => $series->uuid])
->set('prefix', 'AR')
->set('name', 'Ausgangsrechnung')
->call('save')
->assertHasNoErrors();
expect($series->refresh()->prefix)->toBe('RE')
// The rest of the form still saves.
->and($series->name)->toBe('Ausgangsrechnung');
});
it('will not let the counter be wound back onto a number already issued', function () {
// Gapless AND ascending. A lower number is one that is already printed on
// a document somebody holds.
$series = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail();
$series->update(['next_number' => 25]);
Livewire::actingAs(operator('Owner'), 'operator')
->test(EditInvoiceSeries::class, ['uuid' => $series->uuid])
->set('nextNumber', 7)
->call('save')
->assertHasErrors(['nextNumber']);
expect($series->refresh()->next_number)->toBe(25);
});
it('does let the counter be moved forward', function () {
// Skipping numbers is lawful — reusing them is not. An operator migrating
// from another system needs exactly this.
$series = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail();
Livewire::actingAs(operator('Owner'), 'operator')
->test(EditInvoiceSeries::class, ['uuid' => $series->uuid])
->set('nextNumber', 500)
->call('save')
->assertHasNoErrors();
expect($series->refresh()->next_number)->toBe(500);
});
/** A minimal issued document, enough to make a series non-empty. */
function issueOne(InvoiceSeries $series): Invoice
{
return Invoice::create([
'invoice_series_id' => $series->id,
'number' => $series->prefix.'-2026-0001',
'number_year' => 2026,
'number_sequence' => 1,
'issued_on' => now()->toDateString(),
'snapshot' => ['issuer' => [], 'lines' => []],
'net_cents' => 1900,
'tax_cents' => 380,
'gross_cents' => 2280,
'currency' => 'EUR',
]);
}