Add InvoiceIndexAction

This commit is contained in:
Philipp Lang 2023-12-16 01:13:49 +01:00
parent 15b62e59fc
commit 27cbf8bcd9
13 changed files with 216 additions and 69 deletions

View File

@ -0,0 +1,31 @@
<?php
namespace App\Invoice\Actions;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Invoice\Models\Invoice;
use App\Invoice\Resources\InvoiceResource;
use Illuminate\Pagination\LengthAwarePaginator;
use Inertia\Inertia;
use Inertia\Response;
class InvoiceIndexAction
{
use AsAction;
/**
* @return LengthAwarePaginator<Invoice>
*/
public function handle(): LengthAwarePaginator
{
return Invoice::select('*')->with('positions')->paginate(15);
}
public function asController(): Response
{
return Inertia::render('invoice/Index', [
'data' => InvoiceResource::collection($this->handle()),
]);
}
}

View File

@ -16,6 +16,11 @@ class Invoice extends Model
public $casts = [
'to' => 'json',
'status' => InvoiceStatus::class,
];
public $dates = [
'sent_at',
];
/**

View File

@ -0,0 +1,44 @@
<?php
namespace App\Invoice\Resources;
use App\Invoice\Models\Invoice;
use App\Lib\HasMeta;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Invoice
*/
class InvoiceResource extends JsonResource
{
use HasMeta;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array<string, mixed>
*/
public function toArray($request)
{
return [
'to_name' => $this->to['name'],
'sum_human' => number_format($this->positions->sum('price') / 100, 2, ',', '') . ' €',
'sent_at_human' => $this->sent_at->format('d.m.Y'),
'status' => $this->status->value,
];
}
/**
* @return array<string, mixed>
*/
public static function meta(): array
{
return [
'links' => [
'mass-store' => route('invoice.mass-store'),
]
];
}
}

View File

@ -157,7 +157,6 @@ class MemberResource extends JsonResource
'links' => [
'index' => route('member.index'),
'create' => route('member.create'),
'allpayment' => route('allpayment.page'),
'sendpayment' => route('sendpayment.create'),
],
];

View File

@ -1,28 +0,0 @@
<?php
namespace App\Payment\Actions;
use Inertia;
use Inertia\Response;
use Lorisleiva\Actions\Concerns\AsAction;
class AllpaymentPageAction
{
use AsAction;
/**
* @return array<string, string>
*/
public function handle(): array
{
return [];
}
public function asController(): Response
{
session()->put('menu', 'member');
session()->put('title', 'Rechnungen erstellen');
return Inertia::render('allpayment/VForm', $this->handle());
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Factories\Invoice\Models;
use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Models\Invoice;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\Factory;
use Tests\Feature\Invoice\ReceiverRequestFactory;
/**
* @extends Factory<Invoice>
*/
class InvoiceFactory extends Factory
{
protected $model = Invoice::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'greeting' => $this->faker->words(4, true),
];
}
public function to(ReceiverRequestFactory $to): self
{
return $this->state(['to' => $to->create()]);
}
public function sentAt(Carbon $sentAt): self
{
return $this->state(['sent_at' => $sentAt]);
}
public function status(InvoiceStatus $status): self
{
return $this->state(['status' => $status->value]);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Database\Factories\Invoice\Models;
use App\Invoice\Models\InvoicePosition;
use App\Member\Member;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<InvoicePosition>
*/
class InvoicePositionFactory extends Factory
{
protected $model = InvoicePosition::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'description' => $this->faker->words(4, true),
'member_id' => Member::factory()->defaults()->create()->id,
];
}
public function price(int $price): self
{
return $this->state(['price' => $price]);
}
}

View File

@ -18,6 +18,7 @@ return new class extends Migration
$table->json('to');
$table->string('greeting');
$table->string('status');
$table->date('sent_at')->nullable();
$table->timestamps();
});

View File

@ -1,31 +0,0 @@
<template>
<page-layout>
<form class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="year" v-model="inner.year" label="Jahr" required></f-text>
<f-switch id="for_promise" label="Versprechen einbeziehen" v-model="inner.for_promise" size="sm"></f-switch>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
</page-layout>
</template>
<script>
export default {
data: function () {
return {
inner: {
for_promise: false,
},
};
},
props: {},
methods: {
submit() {
this.$inertia.post(`/allpayment`, this.inner);
},
},
};
</script>

View File

@ -25,6 +25,7 @@ use App\Initialize\Actions\InitializeFormAction;
use App\Initialize\Actions\NamiGetSearchLayerAction;
use App\Initialize\Actions\NamiLoginCheckAction;
use App\Initialize\Actions\NamiSearchAction;
use App\Invoice\Actions\InvoiceIndexAction;
use App\Invoice\Actions\MassStoreAction;
use App\Maildispatcher\Actions\CreateAction;
use App\Maildispatcher\Actions\DestroyAction;
@ -46,8 +47,6 @@ use App\Membership\Actions\MembershipDestroyAction;
use App\Membership\Actions\MembershipStoreAction;
use App\Membership\Actions\MembershipUpdateAction;
use App\Membership\Actions\StoreForGroupAction;
use App\Payment\Actions\AllpaymentPageAction;
use App\Payment\Actions\AllpaymentStoreAction;
use App\Payment\Actions\DisplayPdfAction;
use App\Payment\Actions\IndexAction as PaymentIndexAction;
use App\Payment\Actions\PaymentDestroyAction;
@ -113,10 +112,10 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::delete('/payment/{payment}', PaymentDestroyAction::class)->name('payment.destroy');
// -------------------------------- allpayment ---------------------------------
Route::get('allpayment', AllpaymentPageAction::class)->name('allpayment.page');
Route::post('allpayment', MassStoreAction::class)->name('allpayment.store');
Route::post('/invoice/mass-store', MassStoreAction::class)->name('invoice.mass-store');
// ---------------------------------- invoice ----------------------------------
Route::get('/invoice', InvoiceIndexAction::class)->name('invoice.index');
Route::post('/invoice', InvoiceStoreAction::class)->name('invoice.store');
// --------------------------------- membership --------------------------------

View File

@ -0,0 +1,34 @@
<?php
namespace Tests\Feature\Invoice;
use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Models\Invoice;
use App\Invoice\Models\InvoicePosition;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class InvoiceIndexActionTest extends TestCase
{
use DatabaseTransactions;
public function testItDisplaysInvoices(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()
->has(InvoicePosition::factory()->price(1100), 'positions')
->has(InvoicePosition::factory()->price(2200), 'positions')
->to(ReceiverRequestFactory::new()->name('Familie Blabla'))
->sentAt(now()->subDay())
->status(InvoiceStatus::SENT)
->create();
$this->get(route('invoice.index'))
->assertInertiaPath('data.data.0.to_name', 'Familie Blabla')
->assertInertiaPath('data.data.0.sum_human', '33,00 €')
->assertInertiaPath('data.data.0.sent_at_human', now()->subDay()->format('d.m.Y'))
->assertInertiaPath('data.data.0.status', 'Rechnung gestellt')
->assertInertiaPath('data.meta.links.mass-store', route('invoice.mass-store'));
}
}

View File

@ -24,7 +24,7 @@ class MassStoreActionTest extends TestCase
{
Member::factory()->defaults()->emailBillKind()->create(['subscription_id' => null]);
$this->postJson(route('allpayment.store'), [
$this->postJson(route('invoice.mass-store'), [
'year' => now()->addYear()->year,
])->assertOk();
@ -35,7 +35,7 @@ class MassStoreActionTest extends TestCase
{
Member::factory()->defaults()->create();
$this->postJson(route('allpayment.store'), [
$this->postJson(route('invoice.mass-store'), [
'year' => now()->addYear()->year,
])->assertOk();
@ -50,7 +50,7 @@ class MassStoreActionTest extends TestCase
new Child('beitrag2 für {name} für {year}', 2290),
]))->emailBillKind()->create(['firstname' => 'Max', 'lastname' => 'Muster', 'address' => 'Maxstr 4', 'zip' => '33445', 'location' => 'Solingen']);
$this->postJson(route('allpayment.store'), [
$this->postJson(route('invoice.mass-store'), [
'year' => now()->addYear()->year,
])->assertOk();
@ -82,7 +82,7 @@ class MassStoreActionTest extends TestCase
$member = Member::factory()->defaults()->for($subscription)->emailBillKind()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
Member::factory()->defaults()->for($subscription)->sameFamilyAs($member)->emailBillKind()->create(['firstname' => 'Jane']);
$this->postJson(route('allpayment.store'), ['year' => now()->addYear()->year])->assertOk();
$this->postJson(route('invoice.mass-store'), ['year' => now()->addYear()->year])->assertOk();
$this->assertDatabaseCount('invoices', 1);
$this->assertDatabaseCount('invoice_positions', 2);
@ -96,7 +96,7 @@ class MassStoreActionTest extends TestCase
$member = Member::factory()->defaults()->for($subscription)->emailBillKind()->create();
Member::factory()->defaults()->for($subscription)->sameFamilyAs($member)->postBillKind()->create();
$this->postJson(route('allpayment.store'), ['year' => now()->addYear()->year])->assertOk();
$this->postJson(route('invoice.mass-store'), ['year' => now()->addYear()->year])->assertOk();
$this->assertDatabaseCount('invoices', 2);
$this->assertDatabaseCount('invoice_positions', 2);

View File

@ -9,8 +9,10 @@ use App\User;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Http;
use Illuminate\Testing\AssertableJsonString;
use Illuminate\Testing\TestResponse;
use Phake;
use PHPUnit\Framework\Assert;
use Symfony\Component\HttpFoundation\File\File;
use Tests\Lib\MakesHttpCalls;
use Tests\Lib\TestsInertia;
@ -29,6 +31,7 @@ abstract class TestCase extends BaseTestCase
parent::setUp();
Auth::fake();
Member::disableGeolocation();
$this->initInertiaTestcase();
}
public function loginNami(int $mglnr = 12345, string $password = 'password', int|Group $groupId = 55): self
@ -117,4 +120,17 @@ abstract class TestCase extends BaseTestCase
$this->assertCount(1, $output, 'Failed to parse output format of pdfinfo');
$this->assertEquals($pageCount, $output[0]);
}
public function initInertiaTestcase(): void
{
TestResponse::macro('assertInertiaPath', function ($path, $value) {
/** @var TestResponse */
$response = $this;
$props = data_get($response->viewData('page'), 'props');
Assert::assertNotNull($props);
$json = new AssertableJsonString($props);
$json->assertPath($path, $value);
return $this;
});
}
}