From 8af0e2a16eb407c35fb77322da6615ad37956d06 Mon Sep 17 00:00:00 2001 From: Philipp Lang Date: Wed, 14 Dec 2022 15:49:12 +0100 Subject: [PATCH] Add payment for promises --- app/Member/MemberController.php | 2 +- app/Member/Membership.php | 7 ++ app/Membership/MembershipResource.php | 2 +- app/Payment/Actions/AllpaymentPageAction.php | 28 +++++++ app/Payment/Actions/AllpaymentStoreAction.php | 83 +++++++++++++++++++ app/Payment/AllpaymentController.php | 37 --------- app/Payment/Subscription.php | 2 +- .../factories/Payment/SubscriptionFactory.php | 6 ++ ...644_create_subscription_children_table.php | 1 + routes/web.php | 6 +- tests/Feature/Payment/AllpaymentTest.php | 83 +++++++++++++++++++ 11 files changed, 215 insertions(+), 42 deletions(-) create mode 100644 app/Payment/Actions/AllpaymentPageAction.php create mode 100644 app/Payment/Actions/AllpaymentStoreAction.php delete mode 100644 app/Payment/AllpaymentController.php diff --git a/app/Member/MemberController.php b/app/Member/MemberController.php index 95ae50c5..14737746 100644 --- a/app/Member/MemberController.php +++ b/app/Member/MemberController.php @@ -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; diff --git a/app/Member/Membership.php b/app/Member/Membership.php index 0bb0d86d..ff7eba25 100644 --- a/app/Member/Membership.php +++ b/app/Member/Membership.php @@ -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 + */ + public $casts = [ + 'promised_at' => 'date', + ]; + public function activity(): BelongsTo { return $this->belongsTo(Activity::class); diff --git a/app/Membership/MembershipResource.php b/app/Membership/MembershipResource.php index b0faf1a8..f4a1792b 100644 --- a/app/Membership/MembershipResource.php +++ b/app/Membership/MembershipResource.php @@ -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'), ]; } } diff --git a/app/Payment/Actions/AllpaymentPageAction.php b/app/Payment/Actions/AllpaymentPageAction.php new file mode 100644 index 00000000..504bec68 --- /dev/null +++ b/app/Payment/Actions/AllpaymentPageAction.php @@ -0,0 +1,28 @@ + + */ + 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()); + } +} diff --git a/app/Payment/Actions/AllpaymentStoreAction.php b/app/Payment/Actions/AllpaymentStoreAction.php new file mode 100644 index 00000000..66dfd68a --- /dev/null +++ b/app/Payment/Actions/AllpaymentStoreAction.php @@ -0,0 +1,83 @@ + + */ + 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 + */ + 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'); + } +} diff --git a/app/Payment/AllpaymentController.php b/app/Payment/AllpaymentController.php deleted file mode 100644 index 7dc3c92d..00000000 --- a/app/Payment/AllpaymentController.php +++ /dev/null @@ -1,37 +0,0 @@ -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'); - } -} diff --git a/app/Payment/Subscription.php b/app/Payment/Subscription.php index 33a32b1c..d0ff7267 100644 --- a/app/Payment/Subscription.php +++ b/app/Payment/Subscription.php @@ -15,7 +15,7 @@ class Subscription extends Model /** * @var array */ - public $fillable = ['name', 'fee_id', 'split']; + public $fillable = ['name', 'fee_id', 'split', 'for_promise']; /** * @var array diff --git a/database/factories/Payment/SubscriptionFactory.php b/database/factories/Payment/SubscriptionFactory.php index 595f9fa5..b8575eb5 100644 --- a/database/factories/Payment/SubscriptionFactory.php +++ b/database/factories/Payment/SubscriptionFactory.php @@ -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]); + } } diff --git a/database/migrations/2022_12_13_203644_create_subscription_children_table.php b/database/migrations/2022_12_13_203644_create_subscription_children_table.php index b90ccc22..e390c5f8 100644 --- a/database/migrations/2022_12_13_203644_create_subscription_children_table.php +++ b/database/migrations/2022_12_13_203644_create_subscription_children_table.php @@ -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) { diff --git a/routes/web.php b/routes/web.php index 0d9ef488..5e6dfdce 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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'); diff --git a/tests/Feature/Payment/AllpaymentTest.php b/tests/Feature/Payment/AllpaymentTest.php index d9eeae1b..e66070b6 100644 --- a/tests/Feature/Payment/AllpaymentTest.php +++ b/tests/Feature/Payment/AllpaymentTest.php @@ -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); + } }