diff --git a/app/Invoice/Actions/InvoiceIndexAction.php b/app/Invoice/Actions/InvoiceIndexAction.php index d2796fe9..f9472e4c 100644 --- a/app/Invoice/Actions/InvoiceIndexAction.php +++ b/app/Invoice/Actions/InvoiceIndexAction.php @@ -5,9 +5,11 @@ namespace App\Invoice\Actions; use Lorisleiva\Actions\Concerns\AsAction; use App\Invoice\Models\Invoice; use App\Invoice\Resources\InvoiceResource; +use App\Invoice\Scopes\InvoiceFilterScope; use Illuminate\Pagination\LengthAwarePaginator; use Inertia\Inertia; use Inertia\Response; +use Lorisleiva\Actions\ActionRequest; class InvoiceIndexAction { @@ -17,18 +19,20 @@ class InvoiceIndexAction /** * @return LengthAwarePaginator */ - public function handle(): LengthAwarePaginator + public function handle(InvoiceFilterScope $filter): LengthAwarePaginator { - return Invoice::select('*')->with('positions')->paginate(15); + return Invoice::withFilter($filter)->with('positions')->paginate(15); } - public function asController(): Response + public function asController(ActionRequest $request): Response { session()->put('menu', 'invoice'); session()->put('title', 'Rechnungen'); + $filter = InvoiceFilterScope::fromRequest($request->input('filter', '')); + return Inertia::render('invoice/Index', [ - 'data' => InvoiceResource::collection($this->handle()), + 'data' => InvoiceResource::collection($this->handle($filter)), ]); } } diff --git a/app/Invoice/Enums/InvoiceStatus.php b/app/Invoice/Enums/InvoiceStatus.php index 2faf36ad..cd81cf17 100644 --- a/app/Invoice/Enums/InvoiceStatus.php +++ b/app/Invoice/Enums/InvoiceStatus.php @@ -18,6 +18,23 @@ enum InvoiceStatus: string return collect(static::cases())->map(fn ($case) => $case->value); } + /** + * @return Collection + */ + public static function defaultVisibleValues(): Collection + { + return collect(static::cases())->filter(fn ($value) => $value->defaultVisible())->map(fn ($case) => $case->value); + } + + public function defaultVisible(): bool + { + return match ($this) { + static::NEW => true, + static::SENT => true, + static::PAID => false + }; + } + /** * @return array */ diff --git a/app/Invoice/Models/Invoice.php b/app/Invoice/Models/Invoice.php index 1c30a30f..a0678852 100644 --- a/app/Invoice/Models/Invoice.php +++ b/app/Invoice/Models/Invoice.php @@ -7,6 +7,7 @@ use App\Invoice\BillKind; use App\Invoice\Enums\InvoiceStatus; use App\Invoice\InvoiceDocument; use App\Invoice\RememberDocument; +use App\Invoice\Scopes\InvoiceFilterScope; use App\Member\Member; use App\Payment\Subscription; use Illuminate\Database\Eloquent\Builder; @@ -119,6 +120,15 @@ class Invoice extends Model ->where('last_remembered_at', '<=', now()->subMonths(3)); } + /** + * @param Builder $query + * @return Builder + */ + public function scopeWithFilter(Builder $query, InvoiceFilterScope $filter): Builder + { + return $filter->apply($query); + } + public function getMailRecipient(): stdClass { return (object) [ diff --git a/app/Invoice/Resources/InvoiceResource.php b/app/Invoice/Resources/InvoiceResource.php index 39679330..99ed955d 100644 --- a/app/Invoice/Resources/InvoiceResource.php +++ b/app/Invoice/Resources/InvoiceResource.php @@ -5,6 +5,7 @@ namespace App\Invoice\Resources; use App\Invoice\BillKind; use App\Invoice\Enums\InvoiceStatus; use App\Invoice\Models\Invoice; +use App\Invoice\Scopes\InvoiceFilterScope; use App\Lib\HasMeta; use App\Member\Member; use App\Payment\Subscription; @@ -62,6 +63,7 @@ class InvoiceResource extends JsonResource 'statuses' => InvoiceStatus::forSelect(), 'members' => Member::forSelect(), 'subscriptions' => Subscription::forSelect(), + 'filter' => InvoiceFilterScope::fromRequest(request()->input('filter', '')), 'default' => [ 'to' => [ 'name' => '', diff --git a/app/Invoice/Scopes/InvoiceFilterScope.php b/app/Invoice/Scopes/InvoiceFilterScope.php new file mode 100644 index 00000000..0078cc3b --- /dev/null +++ b/app/Invoice/Scopes/InvoiceFilterScope.php @@ -0,0 +1,45 @@ + + */ +#[MapInputName(SnakeCaseMapper::class)] +#[MapOutputName(SnakeCaseMapper::class)] +class InvoiceFilterScope extends Filter +{ + /** + * @param array $statuses + */ + public function __construct( + public ?array $statuses = null, + ) { + } + + /** + * @inheritdoc + */ + public function apply(Builder $query): Builder + { + $query = $query->whereIn('status', $this->statuses); + + return $query; + } + + public function toDefault(): self + { + $this->statuses = $this->statuses === null ? InvoiceStatus::defaultVisibleValues()->toArray() : $this->statuses; + return $this; + } +} diff --git a/app/Lib/Filter.php b/app/Lib/Filter.php index 7207e24c..12f3f749 100644 --- a/app/Lib/Filter.php +++ b/app/Lib/Filter.php @@ -36,6 +36,11 @@ abstract class Filter extends Data */ public static function fromPost(?array $post = null): static { - return static::withoutMagicalCreationFrom($post ?: []); + return static::withoutMagicalCreationFrom($post ?: [])->toDefault(); + } + + public function toDefault(): self + { + return $this; } } diff --git a/resources/js/views/invoice/Index.vue b/resources/js/views/invoice/Index.vue index d844af43..aa47bc44 100644 --- a/resources/js/views/invoice/Index.vue +++ b/resources/js/views/invoice/Index.vue @@ -72,6 +72,17 @@ + + + @@ -118,7 +129,7 @@ import {ref} from 'vue'; import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js'; const props = defineProps(indexProps); -var {axios, meta, data, reloadPage, create, single, edit, cancel, submit, remove} = useIndex(props.data, 'invoice'); +var {axios, meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter} = useIndex(props.data, 'invoice'); const massstore = ref(null); const deleting = ref(null); const forMember = ref({member_id: null, subscription_id: null, year: null}); diff --git a/tests/Feature/Invoice/InvoiceIndexActionTest.php b/tests/Feature/Invoice/InvoiceIndexActionTest.php index ee46acac..00c7c3b4 100644 --- a/tests/Feature/Invoice/InvoiceIndexActionTest.php +++ b/tests/Feature/Invoice/InvoiceIndexActionTest.php @@ -56,6 +56,7 @@ class InvoiceIndexActionTest extends TestCase ->assertInertiaPath('data.meta.statuses.0', ['id' => 'Neu', 'name' => 'Neu']) ->assertInertiaPath('data.meta.members.0', ['id' => $member->id, 'name' => 'Aaaa Aaab']) ->assertInertiaPath('data.meta.subscriptions.0', ['name' => 'Beitrag', 'id' => $subscription->id]) + ->assertInertiaPath('data.meta.filter.statuses', ['Neu', 'Rechnung gestellt']) ->assertInertiaPath('data.meta.default', [ 'to' => [ 'name' => '', @@ -86,4 +87,18 @@ class InvoiceIndexActionTest extends TestCase $this->get(route('invoice.index')) ->assertInertiaPath('data.data.0.sent_at_human', ''); } + + public function testItFiltersForInvoiceStatus(): void + { + $this->login()->loginNami()->withoutExceptionHandling(); + Invoice::factory()->status(InvoiceStatus::NEW)->create(); + Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create(); + Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create(); + + $this->callFilter('invoice.index', [])->assertInertiaCount('data.data', 3); + $this->callFilter('invoice.index', ['statuses' => []])->assertInertiaCount('data.data', 0); + $this->callFilter('invoice.index', ['statuses' => ['Neu']])->assertInertiaCount('data.data', 1); + $this->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen']])->assertInertiaCount('data.data', 4); + $this->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']])->assertInertiaCount('data.data', 6); + } }
Empfänger