Add payment for promises
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Philipp Lang 2022-12-14 15:49:12 +01:00
parent 9740ea2be4
commit 8af0e2a16e
11 changed files with 215 additions and 42 deletions

View File

@ -43,7 +43,7 @@ class MemberController extends Controller
$payload = app(MemberView::class)->index($request, $query['filter']);
$payload['toolbar'] = [
['href' => route('member.create'), 'label' => 'Mitglied anlegen', 'color' => 'primary', 'icon' => 'plus'],
['href' => route('allpayment.create'), 'label' => 'Rechnungen erstellen', 'color' => 'primary', 'icon' => 'invoice', 'show' => $settings->hasModule('bill')],
['href' => route('allpayment.page'), 'label' => 'Rechnungen erstellen', 'color' => 'primary', 'icon' => 'invoice', 'show' => $settings->hasModule('bill')],
['href' => route('sendpayment.create'), 'label' => 'Rechnungen versenden', 'color' => 'info', 'icon' => 'envelope', 'show' => $settings->hasModule('bill')],
];
$payload['query'] = $query;

View File

@ -18,6 +18,13 @@ class Membership extends Model
public $fillable = ['subactivity_id', 'activity_id', 'group_id', 'member_id', 'nami_id', 'from', 'promised_at'];
/**
* @var array<string, string>
*/
public $casts = [
'promised_at' => 'date',
];
public function activity(): BelongsTo
{
return $this->belongsTo(Activity::class);

View File

@ -25,7 +25,7 @@ class MembershipResource extends JsonResource
'subactivity_id' => $this->subactivity_id,
'subactivity_name' => $this->subactivity?->name,
'human_date' => $this->created_at->format('d.m.Y'),
'promised_at' => $this->promised_at,
'promised_at' => $this->promised_at?->format('Y-m-d'),
];
}
}

View File

@ -0,0 +1,28 @@
<?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,83 @@
<?php
namespace App\Payment\Actions;
use App\Member\Member;
use App\Member\Membership;
use App\Payment\Status;
use App\Payment\Subscription;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\RedirectResponse;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class AllpaymentStoreAction
{
use AsAction;
/**
* @return array<string, string>
*/
public function rules(): array
{
return [
'year' => 'required|numeric',
'for_promise' => 'present|boolean',
];
}
public function handle(int $year, bool $forPromise): void
{
foreach (Member::payable()->whereNoPayment($year)->get() as $member) {
$member->createPayment([
'nr' => $year,
'subscription_id' => $member->subscription_id,
'status_id' => Status::default(),
]);
if (!$forPromise) {
continue;
}
$this->createPaymentsForPromise($member, $year);
}
}
private function createPaymentsForPromise(Member $member, int $year): void
{
$subscription = Subscription::firstWhere('for_promise', true);
if (is_null($subscription)) {
return;
}
foreach ($this->promisedMemberships($member, $year) as $membership) {
$attributes = [
'nr' => $membership->subactivity->name.' '.$membership->promised_at->year,
'subscription_id' => $subscription->id,
];
if (!$member->payments()->where($attributes)->exists()) {
$member->createPayment([
...$attributes,
'status_id' => Status::default(),
]);
}
}
}
/**
* @return Collection<Membership>
*/
public function promisedMemberships(Member $member, int $year): Collection
{
return $member->memberships()->whereNotNull('promised_at')->whereYear('promised_at', now()->year($year)->subYear())->get();
}
public function asController(ActionRequest $request): RedirectResponse
{
$this->handle($request->year, $request->for_promise);
return redirect()->back()->success('Zahlungen erstellt');
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace App\Payment;
use App\Http\Controllers\Controller;
use App\Member\Member;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Response;
class AllpaymentController extends Controller
{
public function create(): Response
{
session()->put('menu', 'member');
session()->put('title', 'Rechnungen erstellen');
return \Inertia::render('allpayment/VForm');
}
public function store(Request $request): RedirectResponse
{
$request->validate([
'year' => 'required|numeric',
]);
foreach (Member::payable()->whereNoPayment($request->year)->get() as $member) {
$member->createPayment([
'nr' => $request->year,
'subscription_id' => $member->subscription_id,
'status_id' => Status::default(),
]);
}
return redirect()->back()->success('Zahlungen erstellt');
}
}

View File

@ -15,7 +15,7 @@ class Subscription extends Model
/**
* @var array<int, string>
*/
public $fillable = ['name', 'fee_id', 'split'];
public $fillable = ['name', 'fee_id', 'split', 'for_promise'];
/**
* @var array<string, string>

View File

@ -17,6 +17,7 @@ class SubscriptionFactory extends Factory
return [
'name' => $this->faker->word,
'fee_id' => Fee::factory()->createOne()->id,
'for_promise' => false,
];
}
@ -38,4 +39,9 @@ class SubscriptionFactory extends Factory
return $instance;
}
public function forPromise(): self
{
return $this->state(['for_promise' => true]);
}
}

View File

@ -18,6 +18,7 @@ return new class() extends Migration {
Schema::table('subscriptions', function (Blueprint $table) {
$table->dropColumn('amount');
$table->boolean('split')->default(false);
$table->boolean('for_promise')->default(false);
});
Schema::create('subscription_children', function (Blueprint $table) {

View File

@ -13,7 +13,8 @@ use App\Member\MemberController;
use App\Membership\Actions\MembershipDestroyAction;
use App\Membership\Actions\MembershipStoreAction;
use App\Membership\Actions\MembershipUpdateAction;
use App\Payment\AllpaymentController;
use App\Payment\Actions\AllpaymentPageAction;
use App\Payment\Actions\AllpaymentStoreAction;
use App\Payment\PaymentController;
use App\Payment\SendpaymentController;
use App\Payment\SubscriptionController;
@ -30,7 +31,8 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::resource('member', MemberController::class)->except('show');
Route::get('/member/{member}', MemberShowAction::class)->name('member.show');
Route::apiResource('member.payment', PaymentController::class);
Route::resource('allpayment', AllpaymentController::class);
Route::get('allpayment', AllpaymentPageAction::class)->name('allpayment.page');
Route::post('allpayment', AllpaymentStoreAction::class)->name('allpayment.store');
Route::resource('subscription', SubscriptionController::class);
Route::get('/member/{member}/pdf', MemberPdfController::class)
->name('member.singlepdf');

View File

@ -3,7 +3,10 @@
namespace Tests\Feature\Payment;
use App\Member\Member;
use App\Member\Membership;
use App\Payment\Payment;
use App\Payment\Status;
use App\Payment\Subscription;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
@ -49,6 +52,7 @@ class AllpaymentTest extends TestCase
$response = $this->from('/allpayment/create')->post('allpayment', [
'year' => now()->addYear()->year,
'for_promise' => false,
]);
$response->assertRedirect('/allpayment/create');
@ -59,4 +63,83 @@ class AllpaymentTest extends TestCase
'status_id' => Status::first()->id,
]);
}
public function testItCreatesPromisePayments(): void
{
$member = Member::factory()
->defaults()
->emailBillKind()
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYear()->startOfYear()))
->create();
$subscription = Subscription::factory()->forPromise()->create();
$this->from('/allpayment/create')->post('allpayment', [
'year' => now()->year,
'for_promise' => true,
]);
$this->assertDatabaseHas('payments', [
'member_id' => $member->id,
'nr' => 'Rover '.now()->subYear()->year,
'subscription_id' => $subscription->id,
'status_id' => Status::first()->id,
]);
}
public function testItDoesntCreatePromisePaymentsWhenPromiseIsOver(): void
{
$member = Member::factory()
->defaults()
->emailBillKind()
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYears(2)->startOfYear()))
->create();
$subscription = Subscription::factory()->forPromise()->create();
$this->from('/allpayment/create')->post('allpayment', [
'year' => now()->year,
'for_promise' => true,
]);
$this->assertDatabaseMissing('payments', [
'subscription_id' => $subscription->id,
]);
}
public function testItDoesntCreatePromisePaymentsWhenUserAlreadyHasPayment(): void
{
$subscription = Subscription::factory()->forPromise()->create();
$member = Member::factory()
->defaults()
->emailBillKind()
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYear()->startOfYear()))
->has(Payment::factory()->notPaid()->nr('Rover '.now()->subYear()->year)->for($subscription))
->create();
$this->from('/allpayment/create')->post('allpayment', [
'year' => now()->year,
'for_promise' => true,
]);
$this->assertCount(2, $member->payments);
}
public function testItDoesntCreatePromisePaymentsWhenNoSubscriptionFound(): void
{
$member = Member::factory()
->defaults()
->emailBillKind()
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYear()->startOfYear()))
->has(Payment::factory()->notPaid()->nr('Rover '.now()->subYear()->year))
->create();
$this->from('/allpayment/create')->post('allpayment', [
'year' => now()->year,
'for_promise' => true,
]);
$this->assertCount(2, $member->payments);
}
}