Compare commits

..

No commits in common. "master" and "gallier" have entirely different histories.

20 changed files with 78 additions and 174 deletions

1
.gitignore vendored
View File

@ -40,4 +40,3 @@ Homestead.json
/public/sprite.svg /public/sprite.svg
/.php-cs-fixer.cache /.php-cs-fixer.cache
/groups.sql /groups.sql
/.phpunit.cache

View File

@ -1,9 +1,5 @@
# Letzte Änderungen # Letzte Änderungen
### 1.12.2
- Zuschussliste Gallier
### 1.12.1 ### 1.12.1
- In Teilnehmer-Liste von Veranstaltungen kann nun sortiert und gefiltert werden. - In Teilnehmer-Liste von Veranstaltungen kann nun sortiert und gefiltert werden.

View File

@ -8,7 +8,7 @@ use App\Contribution\Documents\RdpNrwDocument;
use App\Contribution\Documents\CityRemscheidDocument; use App\Contribution\Documents\CityRemscheidDocument;
use App\Contribution\Documents\CitySolingenDocument; use App\Contribution\Documents\CitySolingenDocument;
use App\Contribution\Documents\CityFrankfurtMainDocument; use App\Contribution\Documents\CityFrankfurtMainDocument;
use App\Contribution\Documents\WuppertalDocument; use App\Contribution\Documents\GallierDocument;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -23,7 +23,7 @@ class ContributionFactory
CityRemscheidDocument::class, CityRemscheidDocument::class,
CityFrankfurtMainDocument::class, CityFrankfurtMainDocument::class,
BdkjHesse::class, BdkjHesse::class,
WuppertalDocument::class, GallierDocument::class,
]; ];
/** /**

View File

@ -8,7 +8,7 @@ use App\Contribution\Traits\HasPdfBackground;
use App\Country; use App\Country;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class WuppertalDocument extends ContributionDocument class GallierDocument extends ContributionDocument
{ {
use HasPdfBackground; use HasPdfBackground;
@ -62,7 +62,7 @@ class WuppertalDocument extends ContributionDocument
public static function getName(): string public static function getName(): string
{ {
return 'Wuppertal'; return 'Gallier';
} }
/** /**

View File

@ -20,7 +20,7 @@ class UpdateParticipantSearchIndexAction
[ [
'filterableAttributes' => [...$form->getFields()->filterables()->getKeys(), 'parent-id'], 'filterableAttributes' => [...$form->getFields()->filterables()->getKeys(), 'parent-id'],
'searchableAttributes' => $form->getFields()->searchables()->getKeys(), 'searchableAttributes' => $form->getFields()->searchables()->getKeys(),
'sortableAttributes' => [...$form->getFields()->sortables()->getKeys(), 'id', 'created_at'], 'sortableAttributes' => [...$form->getFields()->sortables()->getKeys(), 'id'],
'displayedAttributes' => [...$form->getFields()->filterables()->getKeys(), ...$form->getFields()->searchables()->getKeys(), 'id'], 'displayedAttributes' => [...$form->getFields()->filterables()->getKeys(), ...$form->getFields()->searchables()->getKeys(), 'id'],
'pagination' => [ 'pagination' => [
'maxTotalHits' => 1000000, 'maxTotalHits' => 1000000,

View File

@ -112,6 +112,6 @@ class Participant extends Model implements Preventable
/** @return array<string, mixed> */ /** @return array<string, mixed> */
public function toSearchableArray(): array public function toSearchableArray(): array
{ {
return [...$this->data, 'parent-id' => $this->parent_id, 'created_at' => $this->created_at->timestamp]; return [...$this->data, 'parent-id' => $this->parent_id];
} }
} }

View File

@ -6,9 +6,9 @@ use Lorisleiva\Actions\Concerns\AsAction;
use App\Invoice\Models\Invoice; use App\Invoice\Models\Invoice;
use App\Invoice\Resources\InvoiceResource; use App\Invoice\Resources\InvoiceResource;
use App\Invoice\Scopes\InvoiceFilterScope; use App\Invoice\Scopes\InvoiceFilterScope;
use Illuminate\Pagination\LengthAwarePaginator;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
use Laravel\Scout\Builder;
use Lorisleiva\Actions\ActionRequest; use Lorisleiva\Actions\ActionRequest;
class InvoiceIndexAction class InvoiceIndexAction
@ -17,11 +17,11 @@ class InvoiceIndexAction
/** /**
* @return Builder<Invoice> * @return LengthAwarePaginator<Invoice>
*/ */
public function handle(InvoiceFilterScope $filter): Builder public function handle(InvoiceFilterScope $filter): LengthAwarePaginator
{ {
return $filter->getQuery()->query(fn ($q) => $q->with('positions')); return Invoice::withFilter($filter)->with('positions')->paginate(15);
} }
public function asController(ActionRequest $request): Response public function asController(ActionRequest $request): Response
@ -32,7 +32,7 @@ class InvoiceIndexAction
$filter = InvoiceFilterScope::fromRequest($request->input('filter', '')); $filter = InvoiceFilterScope::fromRequest($request->input('filter', ''));
return Inertia::render('invoice/Index', [ return Inertia::render('invoice/Index', [
'data' => InvoiceResource::collection($this->handle($filter)->paginate(15)), 'data' => InvoiceResource::collection($this->handle($filter)),
]); ]);
} }
} }

View File

@ -8,17 +8,27 @@ use Lorisleiva\Actions\ActionRequest;
class InvoiceSettings extends LocalSettings implements Storeable class InvoiceSettings extends LocalSettings implements Storeable
{ {
public ?string $from_long; public string $from_long;
public ?string $from;
public ?string $mobile; public string $from;
public ?string $email;
public ?string $website; public string $mobile;
public ?string $address;
public ?string $place; public string $email;
public ?string $zip;
public ?string $iban; public string $website;
public ?string $bic;
public ?int $rememberWeeks; public string $address;
public string $place;
public string $zip;
public string $iban;
public string $bic;
public int $rememberWeeks;
public static function group(): string public static function group(): string
{ {

View File

@ -17,14 +17,12 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Scout\Searchable;
use stdClass; use stdClass;
class Invoice extends Model class Invoice extends Model
{ {
/** @use HasFactory<InvoiceFactory> */ /** @use HasFactory<InvoiceFactory> */
use HasFactory; use HasFactory;
use Searchable;
public $guarded = []; public $guarded = [];
@ -122,6 +120,15 @@ class Invoice extends Model
->where('last_remembered_at', '<=', now()->subWeeks($weeks)); ->where('last_remembered_at', '<=', now()->subWeeks($weeks));
} }
/**
* @param Builder<self> $query
* @return Builder<self>
*/
public function scopeWithFilter(Builder $query, InvoiceFilterScope $filter): Builder
{
return $filter->apply($query);
}
public function getMailRecipient(): stdClass public function getMailRecipient(): stdClass
{ {
return (object) [ return (object) [
@ -147,20 +154,4 @@ class Invoice extends Model
]); ]);
} }
} }
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'to' => implode(', ', $this->to),
'usage' => $this->usage,
'greeting' => $this->greeting,
'mail_email' => $this->mail_email,
'status' => $this->status->value,
];
}
} }

View File

@ -5,43 +5,42 @@ namespace App\Invoice\Scopes;
use App\Invoice\Enums\InvoiceStatus; use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Models\Invoice; use App\Invoice\Models\Invoice;
use App\Lib\Filter; use App\Lib\Filter;
use App\Lib\ScoutFilter; use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Builder;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Mappers\SnakeCaseMapper;
/** /**
* @extends ScoutFilter<Invoice> * @extends Filter<Invoice>
*/ */
#[MapInputName(SnakeCaseMapper::class)] #[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)] #[MapOutputName(SnakeCaseMapper::class)]
class InvoiceFilterScope extends ScoutFilter class InvoiceFilterScope extends Filter
{ {
/** /**
* @param array<int, string> $statuses * @param array<int, string> $statuses
*/ */
public function __construct( public function __construct(
public ?array $statuses = null, public ?array $statuses = null,
public ?string $search = null
) { ) {
$this->statuses = $this->statuses === null ? InvoiceStatus::defaultVisibleValues()->toArray() : $this->statuses;
} }
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function getQuery(): Builder public function apply(Builder $query): Builder
{ {
$this->search = $this->search ?: ''; $query = $query->whereIn('status', $this->statuses);
return Invoice::search($this->search, function ($engine, string $query, array $options) { return $query;
if (empty($this->statuses)) {
$filter = 'status = "asa6aeruuni4BahC7Wei6ahm1"';
} else {
$filter = collect($this->statuses)->map(fn (string $status) => "status = \"$status\"")->join(' OR ');
} }
return $engine->search($query, [...$options, 'filter' => $filter]);
}); /**
* @inheritdoc
*/
public function toDefault(): self
{
$this->statuses = $this->statuses === null ? InvoiceStatus::defaultVisibleValues()->toArray() : $this->statuses;
return $this;
} }
} }

View File

@ -1,7 +1,6 @@
<?php <?php
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Invoice\Models\Invoice;
use App\Member\Member; use App\Member\Member;
return [ return [
@ -154,17 +153,8 @@ return [
'pagination' => [ 'pagination' => [
'maxTotalHits' => 1000000, 'maxTotalHits' => 1000000,
] ]
],
Invoice::class => [
'filterableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
'searchableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
'sortableAttributes' => [],
'displayedAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
'pagination' => [
'maxTotalHits' => 1000000,
] ]
], ],
], ],
],
]; ];

View File

@ -1,27 +0,0 @@
<?php
use App\Invoice\Models\Invoice;
use Illuminate\Database\Migrations\Migration;
use Laravel\Scout\Console\SyncIndexSettingsCommand;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Artisan::call(SyncIndexSettingsCommand::class);
foreach (Invoice::get() as $invoice) {
$invoice->searchable();
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -1,30 +0,0 @@
<?php
use App\Form\Actions\UpdateParticipantSearchIndexAction;
use App\Form\Models\Form;
use App\Lib\Sorting;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
foreach (Form::get() as $form) {
UpdateParticipantSearchIndexAction::run($form);
foreach ($form->participants as $participant) {
$participant->searchable();
}
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -74,7 +74,6 @@
</ui-popup> </ui-popup>
<page-filter breakpoint="xl" :filterable="false"> <page-filter breakpoint="xl" :filterable="false">
<template #buttons> <template #buttons>
<f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
<f-multipleselect <f-multipleselect
id="statuses" id="statuses"
:options="meta.statuses" :options="meta.statuses"

View File

@ -8,7 +8,7 @@
\pagestyle{empty} \pagestyle{empty}
\setlength{\parindent}{0cm} \setlength{\parindent}{0cm}
\backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {wuppertal.pdf}}} \backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {gallier.pdf}}}
\begin{document} \begin{document}
\noindent \sffamily \noindent \sffamily
@ -19,6 +19,8 @@
\node[anchor=base west] at (34mm,29.62mm) {\bfseries{\small{<<<$niceDateUntil>>>}}}; \node[anchor=base west] at (34mm,29.62mm) {\bfseries{\small{<<<$niceDateUntil>>>}}};
\node[] at (203mm,15.55mm) {\bfseries{\small{<<<$zipLocation>>>}}}; \node[] at (203mm,15.55mm) {\bfseries{\small{<<<$zipLocation>>>}}};
\node[thick, cross out,draw=black,text width=2.4mm, text height=2.4mm, inner sep=0mm] at (17.76mm,47.10mm) {};
@foreach($chunk as $i => $member) @foreach($chunk as $i => $member)
\node[anchor=center, text width=9.75mm, align=center] at ($(19.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$i+1>>>}; \node[anchor=center, text width=9.75mm, align=center] at ($(19.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$i+1>>>};
\node[anchor=center, text width=41.75mm, align=center] at ($(48.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->lastname>>>}; \node[anchor=center, text width=41.75mm, align=center] at ($(48.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->lastname>>>};

View File

@ -80,23 +80,20 @@ it('testItShowsEmptyFilters', function () {
$this->callFilter('form.participant.index', ['data' => []], ['form' => $form])->assertJsonPath('meta.filter.data.check', ParticipantFilterScope::$nan); $this->callFilter('form.participant.index', ['data' => []], ['form' => $form])->assertJsonPath('meta.filter.data.check', ParticipantFilterScope::$nan);
}); });
it('sorts by active colums sorting by default', function (array $sorting, string $by, bool $direction) { it('sorts by active colums sorting by default', function () {
$this->login()->loginNami()->withoutExceptionHandling(); $this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([ $form = Form::factory()->fields([
$this->checkboxField('check'), $this->checkboxField('check'),
$this->checkboxField('vorname'), $this->checkboxField('vorname'),
])->create(); ])->create();
$form->update(['meta' => ['active_columns' => [], 'sorting' => $sorting]]); $form->update(['meta' => ['active_columns' => [], 'sorting' => ['by' => 'vorname', 'direction' => true]]]);
sleep(2); sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form]) $this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk() ->assertOk()
->assertJsonPath('meta.filter.sort.by', $by) ->assertJsonPath('meta.filter.sort.by', 'vorname')
->assertJsonPath('meta.filter.sort.direction', $direction); ->assertJsonPath('meta.filter.sort.direction', true);
})->with([ });
[['by' => 'vorname', 'direction' => true], 'vorname', true],
[['by' => 'created_at', 'direction' => true], 'created_at', true],
]);
it('testItDisplaysHasNamiField', function () { it('testItDisplaysHasNamiField', function () {

View File

@ -3,7 +3,6 @@
namespace Tests; namespace Tests;
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Invoice\Models\Invoice;
use App\Member\Member; use App\Member\Member;
use Laravel\Scout\Console\FlushCommand; use Laravel\Scout\Console\FlushCommand;
use Laravel\Scout\Console\SyncIndexSettingsCommand; use Laravel\Scout\Console\SyncIndexSettingsCommand;
@ -27,7 +26,6 @@ abstract class EndToEndTestCase extends TestCase
config()->set('scout.driver', 'meilisearch'); config()->set('scout.driver', 'meilisearch');
Artisan::call(FlushCommand::class, ['model' => Member::class]); Artisan::call(FlushCommand::class, ['model' => Member::class]);
Artisan::call(FlushCommand::class, ['model' => Form::class]); Artisan::call(FlushCommand::class, ['model' => Form::class]);
Artisan::call(FlushCommand::class, ['model' => Invoice::class]);
Artisan::call(SyncIndexSettingsCommand::class); Artisan::call(SyncIndexSettingsCommand::class);
return $this; return $this;

View File

@ -4,7 +4,7 @@ namespace Tests\Feature\Contribution;
use App\Contribution\Documents\RdpNrwDocument; use App\Contribution\Documents\RdpNrwDocument;
use App\Contribution\Documents\CitySolingenDocument; use App\Contribution\Documents\CitySolingenDocument;
use App\Contribution\Documents\WuppertalDocument; use App\Contribution\Documents\GallierDocument;
use App\Country; use App\Country;
use App\Gender; use App\Gender;
use App\Invoice\InvoiceSettings; use App\Invoice\InvoiceSettings;
@ -87,17 +87,17 @@ dataset('validation', function () {
], ],
[ [
['zipLocation' => ''], ['zipLocation' => ''],
WuppertalDocument::class, GallierDocument::class,
'zipLocation', 'zipLocation',
], ],
[ [
['dateFrom' => ''], ['dateFrom' => ''],
WuppertalDocument::class, GallierDocument::class,
'dateFrom', 'dateFrom',
], ],
[ [
['dateUntil' => ''], ['dateUntil' => ''],
WuppertalDocument::class, GallierDocument::class,
'dateUntil', 'dateUntil',
], ],
]; ];
@ -130,7 +130,7 @@ it('compiles documents via api', function (string $type, array $bodyChecks) {
["App\\Contribution\\Documents\\CityRemscheidDocument", ["Max", "Muster", "Jane"]], ["App\\Contribution\\Documents\\CityRemscheidDocument", ["Max", "Muster", "Jane"]],
["App\\Contribution\\Documents\\CityFrankfurtMainDocument", ["Max", "Muster", "Jane"]], ["App\\Contribution\\Documents\\CityFrankfurtMainDocument", ["Max", "Muster", "Jane"]],
["App\\Contribution\\Documents\\BdkjHesse", ["Max", "Muster", "Jane"]], ["App\\Contribution\\Documents\\BdkjHesse", ["Max", "Muster", "Jane"]],
["App\\Contribution\\Documents\\WuppertalDocument", ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]], ["App\\Contribution\\Documents\\GallierDocument", ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]],
]); ]);
it('testItCompilesGroupNameInSolingenDocument', function () { it('testItCompilesGroupNameInSolingenDocument', function () {

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Tests\Feature\EndToEnd; namespace Tests\Feature\Invoice;
use App\Invoice\BillKind; use App\Invoice\BillKind;
use App\Invoice\Enums\InvoiceStatus; use App\Invoice\Enums\InvoiceStatus;
@ -9,11 +9,8 @@ use App\Invoice\Models\InvoicePosition;
use App\Member\Member; use App\Member\Member;
use App\Payment\Subscription; use App\Payment\Subscription;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\EndToEndTestCase;
use Tests\Feature\Invoice\ReceiverRequestFactory;
uses(DatabaseTransactions::class); uses(DatabaseTransactions::class);
uses(EndToEndTestCase::class);
it('testItDisplaysInvoices', function () { it('testItDisplaysInvoices', function () {
$this->login()->loginNami()->withoutExceptionHandling(); $this->login()->loginNami()->withoutExceptionHandling();
@ -28,7 +25,6 @@ it('testItDisplaysInvoices', function () {
->status(InvoiceStatus::SENT) ->status(InvoiceStatus::SENT)
->create(['usage' => 'Usa', 'mail_email' => 'a@b.de']); ->create(['usage' => 'Usa', 'mail_email' => 'a@b.de']);
sleep(2);
test()->get(route('invoice.index')) test()->get(route('invoice.index'))
->assertInertiaPath('data.data.0.to.name', 'Familie Blabla') ->assertInertiaPath('data.data.0.to.name', 'Familie Blabla')
->assertInertiaPath('data.data.0.id', $invoice->id) ->assertInertiaPath('data.data.0.id', $invoice->id)
@ -82,35 +78,19 @@ it('testValuesCanBeNull', function () {
test()->login()->loginNami()->withoutExceptionHandling(); test()->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()->create(); Invoice::factory()->create();
sleep(2);
test()->get(route('invoice.index')) test()->get(route('invoice.index'))
->assertInertiaPath('data.data.0.sent_at_human', ''); ->assertInertiaPath('data.data.0.sent_at_human', '');
}); });
it('filters for invoice status', function (array $filter, int $count) { it('testItFiltersForInvoiceStatus', function () {
test()->login()->loginNami()->withoutExceptionHandling(); test()->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()->status(InvoiceStatus::NEW)->create(); Invoice::factory()->status(InvoiceStatus::NEW)->create();
Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create(); Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create();
Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create(); Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create();
sleep(2); test()->callFilter('invoice.index', [])->assertInertiaCount('data.data', 3);
test()->callFilter('invoice.index', $filter)->assertInertiaCount('data.data', $count); test()->callFilter('invoice.index', ['statuses' => []])->assertInertiaCount('data.data', 0);
})->with([ test()->callFilter('invoice.index', ['statuses' => ['Neu']])->assertInertiaCount('data.data', 1);
[[], 3], test()->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen']])->assertInertiaCount('data.data', 4);
[['statuses' => []], 0], test()->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']])->assertInertiaCount('data.data', 6);
[['statuses' => ['Neu']], 1], });
[['statuses' => ['Neu', 'Rechnung beglichen']], 4],
[['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']], 6],
]);
it('searches invoice usage', function (array $filter, int $count) {
test()->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()->status(InvoiceStatus::NEW)->create(['usage' => 'Kein Zweck']);
Invoice::factory()->status(InvoiceStatus::NEW)->create(['usage' => 'Mitgliedsbeitrag']);
sleep(2);
test()->callFilter('invoice.index', $filter)->assertInertiaCount('data.data', $count);
})->with([
[['search' => 'Mitgliedsbeitrag'], 1],
[['search' => 'Kein'], 1],
]);