Add filter for invoices
continuous-integration/drone/push Build is failing Details

This commit is contained in:
philipp lang 2024-05-14 01:29:39 +02:00
parent 30d670a575
commit c0daff972b
8 changed files with 115 additions and 6 deletions

View File

@ -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<Invoice>
*/
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)),
]);
}
}

View File

@ -18,6 +18,23 @@ enum InvoiceStatus: string
return collect(static::cases())->map(fn ($case) => $case->value);
}
/**
* @return Collection<int, string>
*/
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<int, array{id: string, name: string}>
*/

View File

@ -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<self> $query
* @return Builder<self>
*/
public function scopeWithFilter(Builder $query, InvoiceFilterScope $filter): Builder
{
return $filter->apply($query);
}
public function getMailRecipient(): stdClass
{
return (object) [

View File

@ -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' => '',

View File

@ -0,0 +1,45 @@
<?php
namespace App\Invoice\Scopes;
use App\Form\Models\Form;
use App\Form\Models\Participant;
use App\Invoice\Enums\InvoiceStatus;
use App\Lib\Filter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
/**
* @extends Filter<Participant>
*/
#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
class InvoiceFilterScope extends Filter
{
/**
* @param array<int, string> $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;
}
}

View File

@ -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;
}
}

View File

@ -72,6 +72,17 @@
</section>
</form>
</ui-popup>
<page-filter breakpoint="xl">
<f-multipleselect
id="statuses"
:options="meta.statuses"
:model-value="getFilter('statuses')"
label="Status"
size="sm"
name="group_ids"
@update:model-value="setFilter('statuses', $event)"
></f-multipleselect>
</page-filter>
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
<thead>
<th>Empfänger</th>
@ -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});

View File

@ -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);
}
}