Direct payments for invoices

Events can be moved to archive and moved back
Fixed validation
This commit is contained in:
2026-05-12 16:04:15 +02:00
parent e2fb616565
commit 0cf9602958
42 changed files with 851 additions and 132 deletions
@@ -0,0 +1,22 @@
<?php
namespace App\Domains\Event\Actions\ArchiveEvent;
class ArchiveEventCommand {
public ArchiveEventRequest $request;
public function __construct(ArchiveEventRequest $request) {
$this->request = $request;
}
public function execute(): ArchiveEventResponse {
$response = new ArchiveEventResponse();
$this->request->event->archived = true;
$this->request->event->save();
$response->success = true;
return $response;
}
}
@@ -0,0 +1,13 @@
<?php
namespace App\Domains\Event\Actions\ArchiveEvent;
use App\Models\Event;
class ArchiveEventRequest {
public Event $event;
public function __construct(Event $event) {
$this->event = $event;
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Domains\Event\Actions\ArchiveEvent;
class ArchiveEventResponse {
public bool $success;
public function __construct() {
$this->success = false;
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Domains\Event\Actions\UnarchiveEvent;
class UnarchiveEventCommand {
public UnarchiveEventRequest $request;
public function __construct(UnarchiveEventRequest $request) {
$this->request = $request;
}
public function execute(): UnarchiveEventResponse {
$response = new UnarchiveEventResponse();
$this->request->event->archived = false;
$this->request->event->save();
$response->success = true;
return $response;
}
}
@@ -0,0 +1,13 @@
<?php
namespace App\Domains\Event\Actions\UnarchiveEvent;
use App\Models\Event;
class UnarchiveEventRequest {
public Event $event;
public function __construct(Event $event) {
$this->event = $event;
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Domains\Event\Actions\UnarchiveEvent;
class UnarchiveEventResponse {
public bool $success;
public function __construct() {
$this->success = false;
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Providers\InertiaProvider;
use App\Scopes\CommonController;
use Illuminate\Http\Request;
use Inertia\Response;
class ArchivedEventsController extends CommonController
{
public function __invoke(Request $request): Response {
$events = [];
foreach ($this->events->getEventsByCriteria(['archived' => true]) as $event) {
$events[] = [
'id' => $event->id,
'name' => $event->name,
'location' => $event->location,
'postalCode' => $event->postal_code,
'eventBegin' => $event->start_date->format('d.m.Y'),
'eventEnd' => $event->end_date->format('d.m.Y'),
];
}
return (new InertiaProvider('Event/ArchivedEvents', ['events' => $events]))->render();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Domains\Event\Actions\ArchiveEvent\ArchiveEventCommand;
use App\Domains\Event\Actions\ArchiveEvent\ArchiveEventRequest;
use App\Scopes\CommonController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EventArchiveController extends CommonController
{
public function __invoke(string $eventId, Request $request): JsonResponse {
$event = $this->events->getByIdentifier($eventId);
$archiveEventRequest = new ArchiveEventRequest($event);
$archiveEventCommand = new ArchiveEventCommand($archiveEventRequest);
$response = $archiveEventCommand->execute();
return response()->json([
'status' => $response->success ? 'success' : 'error',
'message' => $response->success ? 'Das Event wurde erfolgreich archiviert.' : 'Beim Archivieren des Events ist ein Fehler aufgetreten.'
]);
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Domains\Event\Controllers;
use App\Domains\Event\Actions\UnarchiveEvent\UnarchiveEventCommand;
use App\Domains\Event\Actions\UnarchiveEvent\UnarchiveEventRequest;
use App\Scopes\CommonController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EventUnarchiveController extends CommonController
{
public function __invoke(int $eventId, Request $request): JsonResponse {
$event = $this->events->getById($eventId);
$unarchiveRequest = new UnarchiveEventRequest($event);
$unarchiveCommand = new UnarchiveEventCommand($unarchiveRequest);
$response = $unarchiveCommand->execute();
return response()->json(['status' => $response->success ? 'success' : 'error']);
}
}
+5 -2
View File
@@ -2,6 +2,8 @@
use App\Domains\Event\Controllers\CreateController;
use App\Domains\Event\Controllers\DetailsController;
use App\Domains\Event\Controllers\EventArchiveController;
use App\Domains\Event\Controllers\EventUnarchiveController;
use App\Domains\Event\Controllers\MailCompose\ByGroupController;
use App\Domains\Event\Controllers\MailCompose\SendController;
use App\Domains\Event\Controllers\ParticipantController;
@@ -26,6 +28,7 @@ Route::prefix('api/v1')
Route::middleware(['auth'])->group(function () {
Route::post('/create', [CreateController::class, 'doCreate']);
Route::prefix('{eventIdentifier}/mailing')->group(function () {
Route::post('/compose/to-group/{groupType}', ByGroupController::class);
Route::post('/send', SendController::class);
@@ -37,8 +40,8 @@ Route::prefix('api/v1')
Route::get('/summary', [DetailsController::class, 'summary']);
Route::get('/participants/{listType}', [DetailsController::class, 'listParticipants']);
Route::post('/unarchive', EventUnarchiveController::class);
Route::get('/archive', EventArchiveController::class);
Route::post('/event-managers', [DetailsController::class, 'updateEventManagers']);
Route::post('/participation-fees', [DetailsController::class, 'updateParticipationFees']);
Route::post('/common-settings', [DetailsController::class, 'updateCommonSettings']);
+2
View File
@@ -1,5 +1,6 @@
<?php
use App\Domains\Event\Controllers\ArchivedEventsController;
use App\Domains\Event\Controllers\AvailableEventsController;
use App\Domains\Event\Controllers\CreateController;
use App\Domains\Event\Controllers\DetailsController;
@@ -24,5 +25,6 @@ Route::middleware(IdentifyTenant::class)->group(function () {
Route::middleware(['auth'])->group(function () {
Route::get('/create-event', CreateController::class);
Route::get('/archived-events', ArchivedEventsController::class);
});
});
@@ -0,0 +1,76 @@
<script setup>
import { ref } from 'vue';
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
import { toast } from 'vue3-toastify';
import axios from 'axios';
const props = defineProps({
events: Array,
});
const events = ref([...props.events]);
async function unarchiveEvent(eventId) {
try {
const response = await axios.post(`/api/v1/event/details/${eventId}/unarchive`);
if (response.data.status === 'success') {
events.value = events.value.filter(e => e.id !== eventId);
toast.success('Veranstaltung wurde aus dem Archiv geholt.');
} else {
toast.error('Fehler beim Wiederherstellen der Veranstaltung.');
}
} catch {
toast.error('Ein unerwarteter Fehler ist aufgetreten.');
}
}
</script>
<template>
<AppLayout title="Archivierte Veranstaltungen">
<div style="width: 95%; margin: 20px auto;">
<h1 style="font-size: 1.5rem; font-weight: 700; margin-bottom: 20px;">Archivierte Veranstaltungen</h1>
<div
v-if="events.length === 0"
style="text-align: center; color: #6b7280; padding: 40px 0;"
>
Es sind keine archivierten Veranstaltungen vorhanden.
</div>
<ShadowedBox
v-for="event in events"
:key="event.id"
style="padding: 20px; margin-bottom: 16px;"
>
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;">
<div>
<h2 style="margin: 0 0 4px 0; font-size: 1.1rem; font-weight: 600;">{{ event.name }}</h2>
<span style="color: #6b7280; font-size: 0.875rem;">
{{ event.postalCode }} {{ event.location }}
&nbsp;·&nbsp;
{{ event.eventBegin }} {{ event.eventEnd }}
</span>
</div>
<button
@click="unarchiveEvent(event.id)"
style="
padding: 8px 20px;
background-color: #f59e0b;
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
white-space: nowrap;
"
>
Aus dem Archiv holen
</button>
</div>
</ShadowedBox>
</div>
</AppLayout>
</template>
@@ -56,6 +56,16 @@
mailCompose.value = true
}
async function archiveEvent() {
const response = await fetch("/api/v1/event/details/" + props.data.event.identifier + "/archive" );
const data = await response.json();
if (data.status === 'success') {
toast.success(data.message)
} else {
toast.error(data.message)
}
}
async function sendPaymentReminder() {
toast.info("Die Nachrichten werden gesendet. Bitte verlasse diese Seite nicht.");
const response = await fetch("/api/v1/event/" + props.data.event.identifier + "/send-payment-reminder/" );
@@ -150,6 +160,7 @@
<label style="font-size: 9pt;" class="link" @click="showEventManagement">Veranstaltungsleitung</label> &nbsp;
<label style="font-size: 9pt;" class="link" @click="showParticipationFees">Teilnahmegebühren</label>
<a style="font-size: 9pt;" class="link" :href="'/cost-unit/' + props.data.event.costUnit.id">Ausgabenübersicht</a>
<a v-if="!dynamicProps.event.registrationAllowed && !dynamicProps.event.archived" style="color: #ff0000; font-size: 9pt;" class="link" @click="archiveEvent">Archivieren</a>
</div>
</div>