Handling ICAL import
This commit is contained in:
71
.ai/conventions.md
Normal file
71
.ai/conventions.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Projektkonventionen
|
||||
|
||||
## Architektur: Actions (Request-Command-Response)
|
||||
|
||||
Jede fachliche Operation wird in eine eigene Action ausgelagert, die aus drei Klassen besteht.
|
||||
Pfad: `app/Domains/{Domain}/Actions/{ActionName}/`
|
||||
|
||||
### Struktur
|
||||
{ActionName}Request.php → Eingabedaten (Konstruktor oder Factory-Methoden) {ActionName}Command.php → Logik, ruft execute(): {ActionName}Response auf {ActionName}Response.php → Rückgabedaten (public Properties)
|
||||
|
||||
|
||||
### Regeln
|
||||
- Der Controller enthält **keine** fachliche Logik – nur Absicherung, Action-Aufruf und HTTP-Response
|
||||
- Commands sind nicht statisch und werden immer instanziiert
|
||||
- Hat ein Request mehrere Varianten, werden **Factory-Methoden** (`forX()`) statt mehrerer Konstruktoren verwendet
|
||||
- Aufrufreihenfolge im Controller: `new Request → new Command(request) → command->execute() → Response verwenden`
|
||||
|
||||
---
|
||||
|
||||
## Controller
|
||||
|
||||
- Alle Controller erben von `App\Scopes\CommonController`
|
||||
- `CommonController` stellt folgende Repositories bereit (keine eigene Instanziierung nötig):
|
||||
- `$this->eventParticipants` → `EventParticipantRepository`
|
||||
- `$this->events` → `EventRepository`
|
||||
- `$this->invoices` → `InvoiceRepository`
|
||||
- `$this->costUnits` → `CostUnitRepository`
|
||||
- `$this->users` → `UserRepository`
|
||||
- `$this->tenant` → aktueller `Tenant`
|
||||
|
||||
---
|
||||
|
||||
## Repositories
|
||||
|
||||
- Datenbankzugriffe gehören **immer** ins Repository, nie direkt in Controller oder Actions
|
||||
- Sicherheitschecks (z. B. „gehört diese Teilnahme dem eingeloggten User?") werden als eigene Repository-Methoden gekapselt
|
||||
- Tenant-Filter: `app('tenant')->slug`
|
||||
- Eingeloggter User: `auth()->user()`
|
||||
|
||||
---
|
||||
|
||||
## Models / Ressourcen
|
||||
|
||||
- Models erben von `App\Scopes\InstancedModel` (mit globalem `SiteScope`)
|
||||
- `$model->toResource()->toArray($request)` liefert das aufbereitete Array über die zugehörige Resource-Klasse
|
||||
- Resource-Klassen liegen in `app/Resources/{ModelName}Resource.php`
|
||||
|
||||
---
|
||||
|
||||
## Tenant
|
||||
|
||||
- Der aktuelle Tenant ist per `app('tenant')` verfügbar (gesetzt durch `IdentifyTenant`-Middleware)
|
||||
- Tenant-Slug: `app('tenant')->slug`
|
||||
- Jede tenant-spezifische DB-Abfrage filtert auf `['tenant' => app('tenant')->slug]`
|
||||
|
||||
---
|
||||
|
||||
## Routing
|
||||
|
||||
- API-Routen liegen in `app/Domains/{Domain}/Routes/api.php`
|
||||
- Alle Routen sind in `IdentifyTenant::class`-Middleware gewrappt
|
||||
- Authentifizierte Routen zusätzlich in `['auth']`-Middleware
|
||||
|
||||
---
|
||||
|
||||
## Mails
|
||||
|
||||
- Mails erben von `Illuminate\Mail\Mailable`
|
||||
- Attachments werden über `Attachment::fromData(fn () => $content, $filename)->withMime(...)` angehängt
|
||||
- Werden Daten sowohl in `content()` als auch in `attachments()` benötigt, wird eine **private Hilfsmethode mit Lazy-Caching** verwendet (einmaliges Berechnen, Ergebnis in private Property speichern)
|
||||
- Blade-Templates referenzieren Mail-Attachments per `cid:`-Link: `<a href="cid:{{ $filename }}">...</a>`
|
||||
@@ -20,7 +20,8 @@ class DashboardController extends CommonController {
|
||||
private function renderForLoggedInUser(Request $request) {
|
||||
$authCheckProvider = new AuthCheckProvider;
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', [
|
||||
'myInvoices' => $this->invoices->getMyInvoicesWidget()
|
||||
'myInvoices' => $this->invoices->getMyInvoicesWidget(),
|
||||
'myParticipations' => $this->eventParticipants->getMyParticipations(),
|
||||
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
@@ -38,4 +39,9 @@ class DashboardController extends CommonController {
|
||||
$costUnits = $this->costUnits->listForSummary(5);
|
||||
return response()->json(['openCostUnits' => $costUnits]);
|
||||
}
|
||||
|
||||
public function getMyParticipations() : JsonResponse {
|
||||
//dd($this->eventParticipants->getMyParticipations());
|
||||
return response()->json(['myParticipations' => $this->eventParticipants->getMyParticipations()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('api/v1/dashboard')->group(function () {
|
||||
Route::get('/my-invoices', [DashboardController::class, 'getMyInvoices']);
|
||||
Route::get('/open-cost-units', [DashboardController::class, 'getOpenCostUnits']);
|
||||
Route::get('/my-participations', [DashboardController::class, 'getMyParticipations']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import MyInvoices from "./Partials/Widgets/MyInvoices.vue";
|
||||
import MyParticipations from "./Partials/Widgets/MyParticipations.vue";
|
||||
|
||||
const props = defineProps({
|
||||
myInvoices: Object,
|
||||
myParticipations: Object,
|
||||
})
|
||||
|
||||
|
||||
@@ -18,10 +20,10 @@ function newInvoice() {
|
||||
<AppLayout title='Dashboard'>
|
||||
<diV class="dashboard-widget-container">
|
||||
<shadowed-box class="dashboard-widget-box" style="width: 60%;">
|
||||
Meine Anmeldungen
|
||||
<MyParticipations />
|
||||
</shadowed-box>
|
||||
|
||||
<shadowed-box class="dashboard-widget-box">
|
||||
<shadowed-box class="dashboard-widget-box" style="height: 275px;">
|
||||
<MyInvoices />
|
||||
<input type="button" value="Neue Abrechnung" @click="newInvoice" style="margin-top: 20px;">
|
||||
|
||||
@@ -45,4 +47,15 @@ function newInvoice() {
|
||||
flex-grow: 1; display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.dashboard-widget-box h2 {
|
||||
border-color: #c0c0c0;
|
||||
border-left-width: 40px;
|
||||
border-left-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
padding: 5px 10px;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -16,6 +16,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Meine Abrechnungen</h2>
|
||||
<p v-for="invoice in myInvoices.myInvoices" class="widget-content-item">
|
||||
<a :href="'/invoice/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
||||
<label>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive} from "vue";
|
||||
import Icon from "../../../../../Views/Components/Icon.vue";
|
||||
|
||||
const myParticipations = reactive({
|
||||
'myParticipations': '',
|
||||
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/dashboard/my-participations');
|
||||
const data = await response.json();
|
||||
Object.assign(myParticipations, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Meine Anmeldungen</h2>
|
||||
<p v-for="participation in myParticipations.myParticipations" class="widget-content-item">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="width: 40%; font-weight: bold;">{{participation.eventName}}</td>
|
||||
<td style="width: 30%; font-weight: bold;">{{participation.arrivalDateReadable}} - {{participation.departureDateReadable}}</td>
|
||||
<td style="width: 30%;">
|
||||
<Icon name="euro-sign" style="padding: 5px; font-size: 11pt; color: #ffffff; margin-right: 5px;" :class="participation.needs_payment ? 'bg-red' : 'bg-green'" />
|
||||
<Icon name="award" style="padding: 5px; font-size: 11pt; color: #ffffff; margin-right: 5px;" :class="participation.cocColor" />
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{participation.event.postal_code}} {{participation.event.location}}<br />
|
||||
</td>
|
||||
<td>
|
||||
<a class="link" :href="`/api/v1/event/participant/${participation.identifier}/ical`">In Kalender importieren</a>
|
||||
</td>
|
||||
<td>
|
||||
{{participation.amountPaid.readable}} / {{participation.amountExpected.readable}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"> </td>
|
||||
<td>eFZ-Status: {{participation.efzStatusReadable}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
<p v-if="myParticipations.myParticipations.length === 0">Du bist aktuelle für keine Veranstaltung angemeldet.</p>
|
||||
<p>
|
||||
<input type="button" value="Für weitere Veranstaltung anmelden" class="button" />
|
||||
</p>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bg-red {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.bg-green {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.bg-yellow {
|
||||
background-color: #f3f353;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcal;
|
||||
|
||||
class GenerateIcalCommand
|
||||
{
|
||||
public function __construct(public GenerateIcalRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): GenerateIcalResponse
|
||||
{
|
||||
$participant = $this->request->participant;
|
||||
$event = $participant->event;
|
||||
|
||||
$uid = $participant->identifier . '@' . app('tenant')->slug;
|
||||
$dtStart = $event->start_date->format('Ymd');
|
||||
$dtEnd = $event->end_date->copy()->addDay()->format('Ymd');
|
||||
$now = now()->format('Ymd\THis\Z');
|
||||
$summary = $this->escapeIcal($event->name);
|
||||
$location = $this->escapeIcal(trim($event->postal_code . ' ' . $event->location));
|
||||
$description = $this->escapeIcal('Teilnahme als: ' . $participant->getOfficialName());
|
||||
|
||||
$icalContent = implode("\r\n", [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//' . app('tenant')->name . '//Veranstaltungskalender//DE',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'BEGIN:VEVENT',
|
||||
'UID:' . $uid,
|
||||
'DTSTAMP:' . $now,
|
||||
'DTSTART;VALUE=DATE:' . $dtStart,
|
||||
'DTEND;VALUE=DATE:' . $dtEnd,
|
||||
'SUMMARY:' . $summary,
|
||||
'LOCATION:' . $location,
|
||||
'DESCRIPTION:' . $description,
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
]) . "\r\n";
|
||||
|
||||
$filename = 'veranstaltung-' . $participant->event()->first()->name . '.ics';
|
||||
|
||||
return new GenerateIcalResponse($icalContent, $filename);
|
||||
}
|
||||
|
||||
private function escapeIcal(string $value): string
|
||||
{
|
||||
return str_replace(
|
||||
['\\', ';', ',', "\n"],
|
||||
['\\\\', '\\;', '\\,', '\\n'],
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcal;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class GenerateIcalRequest
|
||||
{
|
||||
public function __construct(public EventParticipant $participant)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcal;
|
||||
|
||||
class GenerateIcalResponse
|
||||
{
|
||||
public function __construct(
|
||||
public string $icalContent,
|
||||
public string $filename,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcalForDeadline;
|
||||
|
||||
class GenerateIcalForDeadlineCommand {
|
||||
public function __construct(public GenerateIcalForDeadlineRequest $request)
|
||||
{
|
||||
}
|
||||
public function execute(): GenerateIcalForDeadlineResponse
|
||||
{
|
||||
$event = $this->request->event;
|
||||
|
||||
$deadlineDate = $event->registration_final_end->copy()->subDays(2);
|
||||
$dtDate = $deadlineDate->format('Ymd');
|
||||
$now = now()->format('Ymd\THis\Z');
|
||||
$summary = $this->escapeIcal('Zahlungsfrist: ' . $event->name);
|
||||
$description = $this->escapeIcal(
|
||||
'Bitte überweise den Teilnahmebeitrag für "' . $event->name . '" bis zu diesem Datum.'
|
||||
);
|
||||
|
||||
$icalContent = implode("\r\n", [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//' . app('tenant')->name . '//Veranstaltungskalender//DE',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'BEGIN:VEVENT',
|
||||
'UID:payment-deadline-' . $event->identifier . '@' . app('tenant')->slug,
|
||||
'DTSTAMP:' . $now,
|
||||
'DTSTART;VALUE=DATE:' . $dtDate,
|
||||
'DTEND;VALUE=DATE:' . $dtDate,
|
||||
'SUMMARY:' . $summary,
|
||||
'DESCRIPTION:' . $description,
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
]) . "\r\n";
|
||||
|
||||
$filename = 'zahlungsziel-' . $event->name . '.ics';
|
||||
|
||||
return new GenerateIcalForDeadlineResponse($icalContent, $filename);
|
||||
}
|
||||
|
||||
private function escapeIcal(string $value): string
|
||||
{
|
||||
return str_replace(
|
||||
['\\', ';', ',', "\n"],
|
||||
['\\\\', '\\;', '\\,', '\\n'],
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcalForDeadline;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class GenerateIcalForDeadlineRequest {
|
||||
public function __construct(public Event $event) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcalForDeadline;
|
||||
|
||||
class GenerateIcalForDeadlineResponse
|
||||
{
|
||||
public function __construct(
|
||||
public string $icalContent,
|
||||
public string $filename,
|
||||
) {
|
||||
}
|
||||
}
|
||||
31
app/Domains/Event/Controllers/ParticipantIcalController.php
Normal file
31
app/Domains/Event/Controllers/ParticipantIcalController.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalCommand;
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalRequest;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ParticipantIcalController extends CommonController
|
||||
{
|
||||
public function __invoke(string $participantIdentifier, Request $request): Response
|
||||
{
|
||||
$participant = $this->eventParticipants->getMyParticipationByIdentifier($participantIdentifier);
|
||||
|
||||
if ($participant === null) {
|
||||
abort(403, 'Zugriff verweigert.');
|
||||
}
|
||||
|
||||
$icalRequest = new GenerateIcalRequest($participant);
|
||||
$icalCommand = new GenerateIcalCommand($icalRequest);
|
||||
$icalResponse = $icalCommand->execute();
|
||||
|
||||
return response($icalResponse->icalContent, 200, [
|
||||
'Content-Type' => 'text/calendar; charset=utf-8',
|
||||
'Content-Disposition' => 'attachment; filename="' . $icalResponse->filename . '"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalCommand;
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalRequest;
|
||||
use App\Domains\Event\Actions\GenerateIcalForDeadline\GenerateIcalForDeadlineCommand;
|
||||
use App\Domains\Event\Actions\GenerateIcalForDeadline\GenerateIcalForDeadlineRequest;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ParticipantIcalForPaymentController extends CommonController
|
||||
{
|
||||
public function __invoke(string $participantIdentifier, Request $request): Response
|
||||
{
|
||||
$participant = $this->eventParticipants->getMyParticipationByIdentifier($participantIdentifier);
|
||||
|
||||
if ($participant === null) {
|
||||
abort(403, 'Zugriff verweigert.');
|
||||
}
|
||||
|
||||
$icalRequest = new GenerateIcalForDeadlineRequest($participant->event()->first());
|
||||
$icalCommand = new GenerateIcalForDeadlineCommand($icalRequest);
|
||||
$icalResponse = $icalCommand->execute();
|
||||
|
||||
return response($icalResponse->icalContent, 200, [
|
||||
'Content-Type' => 'text/calendar; charset=utf-8',
|
||||
'Content-Disposition' => 'attachment; filename="' . $icalResponse->filename . '"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -159,12 +159,6 @@ class SignupController extends CommonController {
|
||||
'status' => 'success',
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
dd($eventId, $registrationData, $amount);
|
||||
}
|
||||
|
||||
public function calculateAmount(int $eventId, Request $request, bool $forDisplay = true) : JsonResponse | float {
|
||||
|
||||
@@ -5,6 +5,8 @@ use App\Domains\Event\Controllers\DetailsController;
|
||||
use App\Domains\Event\Controllers\MailCompose\ByGroupController;
|
||||
use App\Domains\Event\Controllers\MailCompose\SendController;
|
||||
use App\Domains\Event\Controllers\ParticipantController;
|
||||
use App\Domains\Event\Controllers\ParticipantIcalController;
|
||||
use App\Domains\Event\Controllers\ParticipantIcalForPaymentController;
|
||||
use App\Domains\Event\Controllers\ParticipantPaymentController;
|
||||
use App\Domains\Event\Controllers\ParticipantReSignOnController;
|
||||
use App\Domains\Event\Controllers\ParticipantSignOffController;
|
||||
@@ -45,6 +47,8 @@ Route::prefix('api/v1')
|
||||
|
||||
Route::prefix('/participant/{participantIdentifier}')->group(function () {
|
||||
Route::get('/', ParticipantController::class);
|
||||
Route::get('/ical', ParticipantIcalController::class);
|
||||
Route::get('/ical-payment', ParticipantIcalForPaymentController::class);
|
||||
Route::post('/payment-complete', [ParticipantPaymentController::class, 'paymentComplete']);
|
||||
Route::post('/partial-payment', [ParticipantPaymentController::class, 'partialPaymentComplete']);
|
||||
Route::post('/mark-coc-existing', [ParticipantController::class, 'markCocExisting']);
|
||||
|
||||
@@ -17,6 +17,9 @@ console.log(props.participant)
|
||||
<tr><td>Anreise:</td><td>{{ props.participant.arrival }}</td></tr>
|
||||
<tr><td>Abreise:</td><td>{{ props.participant.departure }}</td></tr>
|
||||
<tr><td>Teilnahmegruppe:</td><td>{{ props.participant.participationType }}</td></tr>
|
||||
<tr><td> </td><td>
|
||||
<a class="link" :href="`/api/v1/event/participant/${props.participant.identifier}/ical`">In Kalender importieren</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<div v-if="props.participant.efz_status === 'NOT_CHECKED'" style="font-weight: bold; color: #b45309; margin-bottom: 20px;">
|
||||
@@ -38,6 +41,9 @@ console.log(props.participant)
|
||||
<tr><td>IBAN:</td><td>{{ props.event.accountIban }}</td></tr>
|
||||
<tr><td>Verwendungszweck:</td><td>{{ props.participant.payment_purpose }}</td></tr>
|
||||
<tr><td>Betrag:</td><td><strong>{{ props.participant.amount_left_string }}</strong></td></tr>
|
||||
<tr><td> </td><td>
|
||||
<a class="link" :href="`/api/v1/event/participant/${props.participant.identifier}/ical-payment`">Erinnerung in Kalender setzen</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
namespace App\Mail\ParticipantParticipationMails;
|
||||
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalCommand;
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalRequest;
|
||||
use App\Domains\Event\Actions\GenerateIcal\GenerateIcalResponse;
|
||||
use App\Domains\Event\Actions\GenerateIcalForDeadline\GenerateIcalForDeadlineCommand;
|
||||
use App\Domains\Event\Actions\GenerateIcalForDeadline\GenerateIcalForDeadlineRequest;
|
||||
use App\Domains\Event\Actions\GenerateIcalForDeadline\GenerateIcalForDeadlineResponse;
|
||||
use App\Models\EventParticipant;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -15,6 +21,10 @@ class EventSignUpSuccessfullMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
private ?GenerateIcalResponse $participationIcal = null;
|
||||
private ?GenerateIcalForDeadlineResponse $deadlineIcal = null;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
@@ -60,6 +70,10 @@ class EventSignUpSuccessfullMail extends Mailable
|
||||
);
|
||||
$girocodeBinary = (string)$girocodeProvider->create();
|
||||
|
||||
$participationIcal = $this->getParticipationIcal();
|
||||
$deadlineIcal = $this->getDeadlineIcal();
|
||||
|
||||
|
||||
return new Content(
|
||||
view: 'emails.events.signup_complete',
|
||||
with: [
|
||||
@@ -76,19 +90,57 @@ class EventSignUpSuccessfullMail extends Mailable
|
||||
'accountIban' => $event['accountIban'],
|
||||
'paymentPurpose' => $participant['payment_purpose'],
|
||||
'girocodeBinary' => $girocodeBinary,
|
||||
'efzStatus' => $participant['efz_status']
|
||||
'efzStatus' => $participant['efz_status'],
|
||||
'participationIcalFilename' => $participationIcal->filename,
|
||||
'deadlineIcalFilename' => $deadlineIcal->filename,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
$attachments = [
|
||||
Attachment::fromData(
|
||||
fn () => $this->getParticipationIcal()->icalContent,
|
||||
$this->getParticipationIcal()->filename
|
||||
)->withMime('text/calendar'),
|
||||
];
|
||||
|
||||
$participant = $this->participant->toResource()->toArray(new Request());
|
||||
|
||||
|
||||
if ($participant['needs_payment']) {
|
||||
/*$attachments[] = [
|
||||
Attachment::fromData(
|
||||
fn () => $this->getDeadlineIcal()->icalContent,
|
||||
$this->getDeadlineIcal()->filename
|
||||
)->withMime('text/calendar'),
|
||||
];*/
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
private function getDeadlineIcal(): GenerateIcalForDeadlineResponse
|
||||
{
|
||||
if ($this->deadlineIcal === null) {
|
||||
$this->deadlineIcal = (new GenerateIcalForDeadlineCommand(
|
||||
new GenerateIcalForDeadlineRequest($this->participant->event()->first())
|
||||
))->execute();
|
||||
}
|
||||
|
||||
return $this->deadlineIcal;
|
||||
}
|
||||
|
||||
private function getParticipationIcal(): GenerateIcalResponse
|
||||
{
|
||||
if ($this->participationIcal === null) {
|
||||
$this->participationIcal = (new GenerateIcalCommand(
|
||||
new GenerateIcalRequest($this->participant)
|
||||
))->execute();
|
||||
}
|
||||
|
||||
return $this->participationIcal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,4 +178,47 @@ class EventParticipantRepository {
|
||||
}
|
||||
return $mailAddresses;
|
||||
}
|
||||
|
||||
public function getMyParticipations(?int $maxEvents = null) : array {
|
||||
$participations = [];
|
||||
$user = auth()->user();
|
||||
if ($user === null) {
|
||||
return $participations;
|
||||
}
|
||||
|
||||
$request = new \Illuminate\Http\Request();
|
||||
|
||||
$query = EventParticipant::where('user_id', $user->id)
|
||||
->whereNull('unregistered_at')
|
||||
->whereHas('event', function ($q) {
|
||||
$q->where('end_date', '>=', now());
|
||||
})
|
||||
->with(['event'])
|
||||
->join('events', 'event_participants.event_id', '=', 'events.id')
|
||||
->orderBy('events.start_date', 'asc')
|
||||
->select('event_participants.*');
|
||||
|
||||
if ($maxEvents !== null) {
|
||||
$query->limit($maxEvents);
|
||||
}
|
||||
|
||||
foreach ($query->get() as $participant) {
|
||||
$participations[] = $participant->toResource()->toArray($request);
|
||||
}
|
||||
|
||||
return $participations;
|
||||
}
|
||||
|
||||
public function getMyParticipationByIdentifier(string $identifier) : ?EventParticipant {
|
||||
$user = auth()->user();
|
||||
if ($user === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return EventParticipant::where('identifier', $identifier)
|
||||
->where('tenant', app('tenant')->slug)
|
||||
->where('user_id', $user->id)
|
||||
->whereNull('unregistered_at')
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class EventParticipantResource extends JsonResource
|
||||
'tetanusVaccination' => $this->resource->tetanus_vaccination?->format('d.m.Y') ?? 'Unbekannt',
|
||||
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
|
||||
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name,
|
||||
'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct,
|
||||
'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct && $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
|
||||
'nicename' => $this->resource->getNicename(),
|
||||
'arrival' => $this->resource->arrival_date->format('d.m.Y'),
|
||||
'departure' => $this->resource->departure_date->format('d.m.Y'),
|
||||
@@ -70,12 +70,21 @@ class EventParticipantResource extends JsonResource
|
||||
'localGroupState' => config('postCode.map.' . $this->resource->localGroup()->first()->postcode),
|
||||
'birthday' => $this->resource->birthday->format('d.m.Y'),
|
||||
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name,
|
||||
'cocColor' => match ($this->resource->efz_status) {
|
||||
EfzStatus::EFZ_STATUS_CHECKED_VALID => 'bg-green',
|
||||
EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'bg-green',
|
||||
EfzStatus::EFZ_STATUS_NOT_CHECKED => 'bg-yellow',
|
||||
EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'bg-red',
|
||||
},
|
||||
'efzStatusReadable' => match($this->resource->efz_status) {
|
||||
EfzStatus::EFZ_STATUS_CHECKED_VALID => 'Gültig',
|
||||
EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'Nicht eingereicht',
|
||||
EfzStatus::EFZ_STATUS_NOT_CHECKED => 'Nicht geprüft',
|
||||
EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'Nicht erforderlich',
|
||||
},
|
||||
'eventName' => $this->resource->event()->first()->name,
|
||||
'arrivalDateReadable' => $this->resource->arrival_date->format('d.m.Y'),
|
||||
'departureDateReadable' => $this->resource->departure_date->format('d.m.Y'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,4 +40,8 @@ import OpenCostUnits from "../../../Domains/Dashboard/Views/Partials/Widgets/Ope
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.widget-box h2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
Abreise
|
||||
</td>
|
||||
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
|
||||
{{ $departure }}
|
||||
{{ $departure }}<br />
|
||||
<a href="cid:{{ $participationIcalFilename }}">In Kalender importieren</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -47,7 +49,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@include('emails.subparts.payment')
|
||||
@include('emails.subparts.payment')<br />
|
||||
</p>
|
||||
|
||||
@else
|
||||
|
||||
Reference in New Issue
Block a user