322 lines
12 KiB
PHP
322 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Enumerations\UserRole;
|
|
use App\Models\DocumentAsset;
|
|
use App\Models\DocumentTemplate;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Die Vorlagen gelten app-weit für alle Mandanten -- Zugriff deshalb nur für Hauptadministrator*innen.
|
|
*/
|
|
class DocumentTemplateAdminTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private Tenant $tenant;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Bewusst kein LV-Tenant: dort leitet AuthCheckProvider die Rolle aus `user_role_main` ab, sodass
|
|
// sich eine Gruppenleitung gar nicht erst darstellen ließe.
|
|
$this->tenant = Tenant::create([
|
|
'slug' => 'wm',
|
|
'name' => 'Wilde Möhre',
|
|
'email' => 'wm@example.com',
|
|
'email_finance' => 'wm-f@example.com',
|
|
'url' => parse_url(config('app.url'), PHP_URL_HOST),
|
|
'account_name' => 'Wilde Möhre e.V.',
|
|
'account_iban' => 'DE00',
|
|
'account_bic' => 'XY',
|
|
'city' => 'Stadt',
|
|
'postcode' => '00000',
|
|
'is_active_local_group' => true,
|
|
'has_active_instance' => true,
|
|
]);
|
|
|
|
app()->instance('tenant', $this->tenant);
|
|
|
|
// `users.user_role_main` und `user_role_local_group` sind Fremdschlüssel auf `user_roles`.
|
|
foreach ([UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_USER] as $role) {
|
|
UserRole::create(['slug' => $role, 'name' => $role]);
|
|
}
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
|
'block' => DocumentTemplate::BLOCK_LAYOUT,
|
|
'content' => '<div>{block:footer}</div>',
|
|
'sort_order' => 10,
|
|
]);
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
|
'block' => 'footer',
|
|
'content' => 'alt',
|
|
'sort_order' => 20,
|
|
]);
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
|
'block' => DocumentTemplate::BLOCK_BODY,
|
|
'content' => '{positions_table}',
|
|
'sort_order' => 30,
|
|
'editable' => false,
|
|
]);
|
|
}
|
|
|
|
private function makeUser(string $mainRole, string $localRole = UserRole::USER_ROLE_USER): User
|
|
{
|
|
return User::create([
|
|
'username' => $mainRole . '-' . uniqid() . '@example.com',
|
|
'email' => $mainRole . '-' . uniqid() . '@example.com',
|
|
'firstname' => 'Test',
|
|
'lastname' => 'Person',
|
|
'password' => bcrypt('secret'),
|
|
'local_group' => $this->tenant->slug,
|
|
'user_role_main' => $mainRole,
|
|
'user_role_local_group' => $localRole,
|
|
'active' => true,
|
|
]);
|
|
}
|
|
|
|
public function test_group_leaders_cannot_reach_the_templates(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_USER, UserRole::USER_ROLE_GROUP_LEADER));
|
|
|
|
$this->get('/admin/document-templates')->assertRedirect('/admin');
|
|
$this->getJson('/api/v1/admin/document-templates')->assertRedirect('/admin');
|
|
}
|
|
|
|
public function test_main_administrators_can_read_the_templates(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
$response = $this->getJson('/api/v1/admin/document-templates');
|
|
|
|
$response->assertOk();
|
|
$response->assertJsonPath('blocks.1.block', 'footer');
|
|
$response->assertJsonPath('blocks.1.editable', true);
|
|
// Der generierte Block wird angezeigt, aber als nicht editierbar gekennzeichnet.
|
|
$response->assertJsonPath('blocks.2.editable', false);
|
|
}
|
|
|
|
public function test_saving_leaves_generated_blocks_untouched(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
$this->postJson('/api/v1/admin/document-templates', [
|
|
'blocks' => [
|
|
'footer' => 'neu',
|
|
DocumentTemplate::BLOCK_BODY => 'manipuliert',
|
|
'gibtesnicht' => 'egal',
|
|
],
|
|
])->assertOk()->assertJsonPath('status', 'success');
|
|
|
|
$blocks = DocumentTemplate::forType(DocumentTemplate::TYPE_PARTICIPANT_INVOICE);
|
|
|
|
$this->assertSame('neu', $blocks['footer']->content);
|
|
$this->assertSame('{positions_table}', $blocks[DocumentTemplate::BLOCK_BODY]->content);
|
|
}
|
|
|
|
public function test_a_bare_asset_placeholder_in_the_css_is_refused(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
|
'block' => DocumentTemplate::BLOCK_STYLE,
|
|
'content' => '.a { color: red; }',
|
|
'sort_order' => 15,
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/admin/document-templates', [
|
|
'blocks' => [
|
|
DocumentTemplate::BLOCK_STYLE => '.a { color: red; }{asset:logo}',
|
|
'footer' => 'neu',
|
|
],
|
|
]);
|
|
|
|
$response->assertOk()->assertJsonPath('status', 'error');
|
|
$this->assertStringContainsString('url("{asset:logo}")', $response->json('message'));
|
|
|
|
// Nichts gespeichert -- auch nicht der unbedenkliche Block.
|
|
$blocks = DocumentTemplate::forType(DocumentTemplate::TYPE_PARTICIPANT_INVOICE);
|
|
$this->assertSame('.a { color: red; }', $blocks[DocumentTemplate::BLOCK_STYLE]->content);
|
|
$this->assertSame('alt', $blocks['footer']->content);
|
|
}
|
|
|
|
public function test_an_asset_inside_url_is_accepted_in_the_css(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
|
'block' => DocumentTemplate::BLOCK_STYLE,
|
|
'content' => '',
|
|
'sort_order' => 15,
|
|
]);
|
|
|
|
$this->postJson('/api/v1/admin/document-templates', [
|
|
'blocks' => [DocumentTemplate::BLOCK_STYLE => '.a { background: url("{asset:logo}"); }'],
|
|
])->assertOk()->assertJsonPath('status', 'success');
|
|
|
|
$this->assertStringContainsString(
|
|
'url("{asset:logo}")',
|
|
DocumentTemplate::where('block', DocumentTemplate::BLOCK_STYLE)->first()->content
|
|
);
|
|
}
|
|
|
|
public function test_preview_renders_unsaved_content(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
$response = $this->post('/api/v1/admin/document-templates/preview', [
|
|
'blocks' => ['footer' => 'noch nicht gespeichert'],
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$response->assertHeader('Content-Type', 'application/pdf');
|
|
// Der gespeicherte Stand bleibt unberührt.
|
|
$this->assertSame('alt', DocumentTemplate::where('block', 'footer')->first()->content);
|
|
}
|
|
|
|
public function test_assets_still_used_in_the_template_are_kept(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentAsset::create(['name' => 'logo', 'mime' => 'image/png', 'data' => 'QUJD']);
|
|
DocumentTemplate::where('block', 'footer')->update(['content' => '<img src="{asset:logo}" />']);
|
|
|
|
$this->deleteJson('/api/v1/admin/document-templates/assets/logo')
|
|
->assertOk()
|
|
->assertJsonPath('status', 'error');
|
|
|
|
$this->assertNotNull(DocumentAsset::where('name', 'logo')->first());
|
|
}
|
|
|
|
public function test_unused_assets_can_be_deleted(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentAsset::create(['name' => 'altbestand', 'mime' => 'image/png', 'data' => 'QUJD']);
|
|
|
|
$this->deleteJson('/api/v1/admin/document-templates/assets/altbestand')
|
|
->assertOk()
|
|
->assertJsonPath('status', 'success');
|
|
|
|
$this->assertNull(DocumentAsset::where('name', 'altbestand')->first());
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Mehrere Dokumentarten
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
public function test_the_default_document_type_is_the_invoice(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
$this->getJson('/api/v1/admin/document-templates')
|
|
->assertOk()
|
|
->assertJsonPath('documentType', DocumentTemplate::TYPE_PARTICIPANT_INVOICE)
|
|
->assertJsonPath('documentTypes.0.value', DocumentTemplate::TYPE_PARTICIPANT_INVOICE)
|
|
->assertJsonPath('documentTypes.1.value', DocumentTemplate::TYPE_PARTICIPANT_REFUND);
|
|
}
|
|
|
|
public function test_the_refund_type_returns_its_own_blocks_and_tokens(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_REFUND,
|
|
'block' => 'subject',
|
|
'content' => 'Rückerstattung {document_number}',
|
|
'sort_order' => 10,
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/admin/document-templates?type=participant_refund');
|
|
|
|
$response->assertOk();
|
|
$response->assertJsonPath('documentType', DocumentTemplate::TYPE_PARTICIPANT_REFUND);
|
|
$response->assertJsonCount(1, 'blocks');
|
|
$response->assertJsonPath('blocks.0.block', 'subject');
|
|
// Die Platzhalter sind die des Belegs, nicht die der Rechnung.
|
|
$response->assertJsonPath('tokenGroups.refund.tokens.refund_amount.description', 'Erstattungsbetrag');
|
|
$response->assertJsonMissingPath('tokenGroups.body.tokens.positions_table');
|
|
}
|
|
|
|
public function test_saving_writes_only_into_the_selected_type(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_REFUND,
|
|
'block' => 'footer',
|
|
'content' => 'Beleg-Fuß',
|
|
'sort_order' => 20,
|
|
]);
|
|
|
|
$this->postJson('/api/v1/admin/document-templates', [
|
|
'type' => DocumentTemplate::TYPE_PARTICIPANT_REFUND,
|
|
'blocks' => ['footer' => 'neuer Beleg-Fuß'],
|
|
])->assertOk()->assertJsonPath('status', 'success');
|
|
|
|
$this->assertSame('neuer Beleg-Fuß', DocumentTemplate::where('document_type', DocumentTemplate::TYPE_PARTICIPANT_REFUND)
|
|
->where('block', 'footer')->first()->content);
|
|
|
|
// Der gleichnamige Block der Rechnung bleibt unberührt.
|
|
$this->assertSame('alt', DocumentTemplate::where('document_type', DocumentTemplate::TYPE_PARTICIPANT_INVOICE)
|
|
->where('block', 'footer')->first()->content);
|
|
}
|
|
|
|
public function test_an_unknown_document_type_is_refused(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
$this->getJson('/api/v1/admin/document-templates?type=erfunden')->assertStatus(422);
|
|
|
|
$this->postJson('/api/v1/admin/document-templates', [
|
|
'type' => 'erfunden',
|
|
'blocks' => ['footer' => 'egal'],
|
|
])->assertStatus(422);
|
|
|
|
$this->post('/api/v1/admin/document-templates/preview', [
|
|
'type' => 'erfunden',
|
|
'blocks' => [],
|
|
])->assertStatus(422);
|
|
|
|
// Die Rechnungsvorlage darf davon nichts abbekommen haben.
|
|
$this->assertSame('alt', DocumentTemplate::where('document_type', DocumentTemplate::TYPE_PARTICIPANT_INVOICE)
|
|
->where('block', 'footer')->first()->content);
|
|
}
|
|
|
|
public function test_preview_renders_the_refund_with_its_sample_data(): void
|
|
{
|
|
$this->actingAs($this->makeUser(UserRole::USER_ROLE_ADMIN));
|
|
|
|
DocumentTemplate::create([
|
|
'document_type' => DocumentTemplate::TYPE_PARTICIPANT_REFUND,
|
|
'block' => DocumentTemplate::BLOCK_LAYOUT,
|
|
'content' => '<div>{document_number}</div>',
|
|
'sort_order' => 10,
|
|
]);
|
|
|
|
$response = $this->post('/api/v1/admin/document-templates/preview', [
|
|
'type' => DocumentTemplate::TYPE_PARTICIPANT_REFUND,
|
|
'blocks' => [],
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$response->assertHeader('Content-Type', 'application/pdf');
|
|
$this->assertStringStartsWith('%PDF', $response->getContent());
|
|
}
|
|
}
|
|
|