Add filter for invoices
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
30d670a575
commit
c0daff972b
|
@ -5,9 +5,11 @@ namespace App\Invoice\Actions;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
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 Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
|
||||||
class InvoiceIndexAction
|
class InvoiceIndexAction
|
||||||
{
|
{
|
||||||
|
@ -17,18 +19,20 @@ class InvoiceIndexAction
|
||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator<Invoice>
|
* @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('menu', 'invoice');
|
||||||
session()->put('title', 'Rechnungen');
|
session()->put('title', 'Rechnungen');
|
||||||
|
|
||||||
|
$filter = InvoiceFilterScope::fromRequest($request->input('filter', ''));
|
||||||
|
|
||||||
return Inertia::render('invoice/Index', [
|
return Inertia::render('invoice/Index', [
|
||||||
'data' => InvoiceResource::collection($this->handle()),
|
'data' => InvoiceResource::collection($this->handle($filter)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,23 @@ enum InvoiceStatus: string
|
||||||
return collect(static::cases())->map(fn ($case) => $case->value);
|
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}>
|
* @return array<int, array{id: string, name: string}>
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,6 +7,7 @@ use App\Invoice\BillKind;
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\InvoiceDocument;
|
use App\Invoice\InvoiceDocument;
|
||||||
use App\Invoice\RememberDocument;
|
use App\Invoice\RememberDocument;
|
||||||
|
use App\Invoice\Scopes\InvoiceFilterScope;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use App\Payment\Subscription;
|
use App\Payment\Subscription;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
@ -119,6 +120,15 @@ class Invoice extends Model
|
||||||
->where('last_remembered_at', '<=', now()->subMonths(3));
|
->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
|
public function getMailRecipient(): stdClass
|
||||||
{
|
{
|
||||||
return (object) [
|
return (object) [
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Invoice\Resources;
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\Models\Invoice;
|
use App\Invoice\Models\Invoice;
|
||||||
|
use App\Invoice\Scopes\InvoiceFilterScope;
|
||||||
use App\Lib\HasMeta;
|
use App\Lib\HasMeta;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use App\Payment\Subscription;
|
use App\Payment\Subscription;
|
||||||
|
@ -62,6 +63,7 @@ class InvoiceResource extends JsonResource
|
||||||
'statuses' => InvoiceStatus::forSelect(),
|
'statuses' => InvoiceStatus::forSelect(),
|
||||||
'members' => Member::forSelect(),
|
'members' => Member::forSelect(),
|
||||||
'subscriptions' => Subscription::forSelect(),
|
'subscriptions' => Subscription::forSelect(),
|
||||||
|
'filter' => InvoiceFilterScope::fromRequest(request()->input('filter', '')),
|
||||||
'default' => [
|
'default' => [
|
||||||
'to' => [
|
'to' => [
|
||||||
'name' => '',
|
'name' => '',
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,11 @@ abstract class Filter extends Data
|
||||||
*/
|
*/
|
||||||
public static function fromPost(?array $post = null): static
|
public static function fromPost(?array $post = null): static
|
||||||
{
|
{
|
||||||
return static::withoutMagicalCreationFrom($post ?: []);
|
return static::withoutMagicalCreationFrom($post ?: [])->toDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toDefault(): self
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,17 @@
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</ui-popup>
|
</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">
|
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Empfänger</th>
|
<th>Empfänger</th>
|
||||||
|
@ -118,7 +129,7 @@
|
||||||
import {ref} from 'vue';
|
import {ref} from 'vue';
|
||||||
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
|
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
|
||||||
const props = defineProps(indexProps);
|
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 massstore = ref(null);
|
||||||
const deleting = ref(null);
|
const deleting = ref(null);
|
||||||
const forMember = ref({member_id: null, subscription_id: null, year: null});
|
const forMember = ref({member_id: null, subscription_id: null, year: null});
|
||||||
|
|
|
@ -56,6 +56,7 @@ class InvoiceIndexActionTest extends TestCase
|
||||||
->assertInertiaPath('data.meta.statuses.0', ['id' => 'Neu', 'name' => 'Neu'])
|
->assertInertiaPath('data.meta.statuses.0', ['id' => 'Neu', 'name' => 'Neu'])
|
||||||
->assertInertiaPath('data.meta.members.0', ['id' => $member->id, 'name' => 'Aaaa Aaab'])
|
->assertInertiaPath('data.meta.members.0', ['id' => $member->id, 'name' => 'Aaaa Aaab'])
|
||||||
->assertInertiaPath('data.meta.subscriptions.0', ['name' => 'Beitrag', 'id' => $subscription->id])
|
->assertInertiaPath('data.meta.subscriptions.0', ['name' => 'Beitrag', 'id' => $subscription->id])
|
||||||
|
->assertInertiaPath('data.meta.filter.statuses', ['Neu', 'Rechnung gestellt'])
|
||||||
->assertInertiaPath('data.meta.default', [
|
->assertInertiaPath('data.meta.default', [
|
||||||
'to' => [
|
'to' => [
|
||||||
'name' => '',
|
'name' => '',
|
||||||
|
@ -86,4 +87,18 @@ class InvoiceIndexActionTest extends TestCase
|
||||||
$this->get(route('invoice.index'))
|
$this->get(route('invoice.index'))
|
||||||
->assertInertiaPath('data.data.0.sent_at_human', '');
|
->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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue