Compare commits
34 Commits
ab711109a7
...
dev-001
| Author | SHA1 | Date | |
|---|---|---|---|
| 94b1d7b2ac | |||
| 1f5f6bc32e | |||
| 9e39435818 | |||
| 096ba07b4c | |||
| 5bcdc2fb5d | |||
| f4ea07d82c | |||
| 21be212129 | |||
| 8348f677a5 | |||
| f813056bf7 | |||
| 1ee6b9968f | |||
| 6f8be58943 | |||
| 2e8daf78e1 | |||
| 4878f750bd | |||
| 33a9271013 | |||
| ff98f0860c | |||
| ed7f887e3a | |||
| e6bd8c684d | |||
| 653e85b781 | |||
| 43f8621053 | |||
| 2d17e61cc8 | |||
| 7bea223ded | |||
| df7c14442e | |||
| 33b4a249cc | |||
| 37039f082c | |||
| 405591d6dd | |||
| b8341890d3 | |||
| 23af267896 | |||
| b1c333648a | |||
| fcf41c5d13 | |||
| 2b458eccd7 | |||
| 4f4dff2edd | |||
| cd526231ed | |||
| fa886aad4d | |||
| f468814a2f |
81
.ai/conventions.md
Normal file
81
.ai/conventions.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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`
|
||||
|
||||
- Die Controller besitzen ausschließlich eine __invoke() - Funktion
|
||||
- Für die Speichern-Actions werden separate Controller-Klassen erstellt (z. B. `StoreEventParticipantController`)
|
||||
---
|
||||
|
||||
## 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>`
|
||||
|
||||
## Actions
|
||||
- Die Actions sind in `app/Domains/{Domain}/Actions/{ActionName}/`-Verzeichnissen organisiert
|
||||
- Jede Action besitzt einen Request, einen Command und einen Response
|
||||
- Der Request besitzt auschließlich einen Konstruktor, der die notwendigen Parameter annimmt
|
||||
- Die Response-Klasse enthält ausschließlich die notwendigen Daten für die Antwort
|
||||
- Die Action-Klasse enthält die Logik für die Verarbeitung der Anfrage und die Generierung der Antwort
|
||||
- Die Logik wird in einer execute() - Funktion innerhalb des Commands impplementiert. Private Funktionen, für ausgelagerte Prozesse sind zulässig, wenn der Code damit lesbarer wird.
|
||||
0
.ai/mcp/mcp.json
Normal file
0
.ai/mcp/mcp.json
Normal file
12
.junie/AGENTS.md
Normal file
12
.junie/AGENTS.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Project Guidelines
|
||||
|
||||
This is a placeholder of the project guidelines for Junie.
|
||||
Replace this text with any project-level instructions for Junie, e.g.:
|
||||
|
||||
* What is the project structure
|
||||
* Whether Junie should run tests to check the correctness of the proposed solution
|
||||
* How does Junie run tests (once it requires any non-standard approach)
|
||||
* Whether Junie should build the project before submitting the result
|
||||
* Any code-style related instructions
|
||||
|
||||
As an option you can ask Junie to create these guidelines for you.
|
||||
34
app/Casts/AmountCast.php
Normal file
34
app/Casts/AmountCast.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AmountCast implements CastsAttributes
|
||||
{
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ?Amount
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Amount((float) $value, 'Euro');
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): ?float
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof Amount) {
|
||||
return $value->getAmount();
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,13 @@ class CreateCostUnitCommand {
|
||||
'mail_on_new' => $this->request->mailOnNew,
|
||||
'allow_new' => true,
|
||||
'archived' => false,
|
||||
|
||||
]);
|
||||
|
||||
if (null !== $costUnit) {
|
||||
$response->costUnit = $costUnit;
|
||||
$response->success = true;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
class CreateCostUnitResponse {
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class CreateCostUnitResponse {
|
||||
public bool $success;
|
||||
public ?CostUnit $costUnit;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->costUnit = null;
|
||||
}
|
||||
}
|
||||
|
||||
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Models\Tenant;
|
||||
use App\Providers\FileWriteProvider;
|
||||
use App\Providers\InvoiceCsvFileProvider;
|
||||
use App\Providers\PainFileProvider;
|
||||
use App\Providers\WebDavProvider;
|
||||
use App\Providers\ZipArchiveFileProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ExportController extends CommonController {
|
||||
public function __invoke(int $costUnitId) {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$invoicesForExport = $this->invoices->getByStatus($costUnit, InvoiceStatus::INVOICE_STATUS_APPROVED, false);
|
||||
|
||||
$webdavProvider = new WebDavProvider(WebDavProvider::INVOICE_PREFIX . $this->tenant->url . '/' . $costUnit->name);
|
||||
|
||||
$painFileData = $this->painData($invoicesForExport);
|
||||
$csvData = $this->csvData($invoicesForExport);
|
||||
|
||||
$filePrefix = Tenant::getTempDirectory();
|
||||
|
||||
$painFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '-sepa.xml', $painFileData);
|
||||
$painFileWriteProvider->writeToFile();
|
||||
|
||||
$csvFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '.csv', $csvData);
|
||||
$csvFileWriteProvider->writeToFile();
|
||||
|
||||
if ($this->tenant->upload_exports) {
|
||||
$webdavProvider->uploadFile($painFileWriteProvider->fileName);
|
||||
$webdavProvider->uploadFile($csvFileWriteProvider->fileName);
|
||||
}
|
||||
|
||||
$downloadZipArchiveFiles = [
|
||||
$painFileWriteProvider->fileName,
|
||||
$csvFileWriteProvider->fileName
|
||||
];
|
||||
|
||||
foreach ($invoicesForExport as $invoice) {
|
||||
$changeStatusRequest = new ChangeStatusRequest($invoice, InvoiceStatus::INVOICE_STATUS_EXPORTED);
|
||||
$changeStatusCommand = new ChangeStatusCommand($changeStatusRequest);
|
||||
$changeStatusCommand->execute();
|
||||
|
||||
if ($this->tenant->download_exports) {
|
||||
$createInvoiceReceiptRequest = new CreateInvoiceReceiptRequest($invoice);
|
||||
$createInvoiceReceiptCommand = new CreateInvoiceReceiptCommand($createInvoiceReceiptRequest);
|
||||
$response = $createInvoiceReceiptCommand->execute();
|
||||
|
||||
$downloadZipArchiveFiles[] = $response->fileName;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->tenant->download_exports) {
|
||||
$zipFile = Tenant::getTempDirectory() . 'Abrechnungen-' . $costUnit->name . '.zip';
|
||||
$zipFileProvider = new ZipArchiveFileProvider($zipFile);
|
||||
foreach ($downloadZipArchiveFiles as $file) {
|
||||
$zipFileProvider->addFile($file);
|
||||
}
|
||||
|
||||
$zipFileProvider->create();
|
||||
foreach ($downloadZipArchiveFiles as $file) {
|
||||
Storage::delete($file);
|
||||
}
|
||||
|
||||
Storage::delete($painFileWriteProvider->fileName);
|
||||
Storage::delete($csvFileWriteProvider->fileName);
|
||||
|
||||
return response()->download(
|
||||
storage_path('app/private/' . $zipFile),
|
||||
basename($zipFile),
|
||||
['Content-Type' => 'application/zip']
|
||||
);
|
||||
}
|
||||
|
||||
Storage::delete($painFileWriteProvider->fileName);
|
||||
Storage::delete($csvFileWriteProvider->fileName);
|
||||
return response()->json([
|
||||
'message' => 'Die Abrechnungen wurden exportiert.' . PHP_EOL .'Die Belege werden asynchron auf dem Webdav-Server hinterlegt.' . PHP_EOL . PHP_EOL . 'Sollten diese in 15 Minuten nicht vollständig sein, kontaktiere den Adminbistrator.'
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
private function painData(array $invoices) : string {
|
||||
$invoicesForPainFile = [];
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($invoice->contact_bank_owner !== null && $invoice->contact_bank_iban !== '' && !$invoice->donation) {
|
||||
$invoicesForPainFile[] = $invoice;
|
||||
}
|
||||
}
|
||||
|
||||
$painFileProvider = new PainFileProvider($this->tenant->account_iban, $this->tenant->account_name, $this->tenant->account_bic, $invoicesForPainFile);
|
||||
return $painFileProvider->createPainFileContent();
|
||||
}
|
||||
|
||||
public function csvData(array $invoices) : string {
|
||||
$csvDateProvider = new InvoiceCsvFileProvider($invoices);
|
||||
return $csvDateProvider->createCsvFileContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,11 @@ use Illuminate\Http\JsonResponse;
|
||||
|
||||
class OpenController extends CommonController {
|
||||
public function __invoke(int $costUnitId) {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/Open', [
|
||||
'costUnitId' => $costUnitId
|
||||
'costUnit' => $costUnit
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use App\Domains\CostUnit\Controllers\ChangeStateController;
|
||||
use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||
use App\Domains\CostUnit\Controllers\EditController;
|
||||
use App\Domains\CostUnit\Controllers\ExportController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\CostUnit\Controllers\OpenController;
|
||||
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
||||
@@ -37,6 +38,7 @@ Route::prefix('api/v1')
|
||||
Route::get('/treasurers', TreasurersEditController::class);
|
||||
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
||||
|
||||
Route::get('/export-payouts', ExportController::class);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
import ListInvoices from "./Partials/ListInvoices.vue";
|
||||
|
||||
const props = defineProps({
|
||||
costUnitId: Number
|
||||
costUnit: Object
|
||||
})
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.cost_unit_id
|
||||
const initialCostUnitId = props.costUnit.id
|
||||
const initialInvoiceId = props.invoice_id
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{
|
||||
title: 'Neue Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/new",
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/new",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
|
||||
@@ -30,21 +30,21 @@
|
||||
{
|
||||
title: 'Nichtexportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/approved",
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/approved",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Exportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/exported",
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/exported",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Abgelehnte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/denied",
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/denied",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
@@ -58,7 +58,7 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Abrechnungen">
|
||||
<AppLayout :title="'Abrechnungen ' + props.costUnit.name">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" :initial-sub-tab-id="initialInvoiceId" />
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
<script setup>
|
||||
import {createApp, ref} from 'vue'
|
||||
/*import {
|
||||
_mareike_download_as_zip,
|
||||
|
||||
_mareike_use_webdav
|
||||
} from "../../../assets/javascripts/library";*/
|
||||
//import LoadingModal from "../../../assets/components/LoadingModal.vue";
|
||||
//import Invoices from '../invoices/index.vue'
|
||||
import LoadingModal from "../../../../Views/Components/LoadingModal.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import CostUnitDetails from "./CostUnitDetails.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import Treasurers from "./Treasurers.vue";
|
||||
|
||||
//import CostUnitDetails from "./CostUnitDetails.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
@@ -42,13 +34,8 @@ const show_cost_unit = ref(false)
|
||||
const showTreasurers = ref(false)
|
||||
const costUnit = ref(null)
|
||||
|
||||
|
||||
const { data, loading, error, request, download } = useAjax()
|
||||
|
||||
if (props.deep_jump_id > 0) {
|
||||
// open_invoice_list(props.deep_jump_id, 'new', props.deep_jump_id_sub)
|
||||
}
|
||||
|
||||
async function costUnitDetails(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
||||
method: "GET",
|
||||
@@ -114,41 +101,45 @@ async function changeCostUnitState(costUnitId, endPoint) {
|
||||
}
|
||||
|
||||
|
||||
async function export_payouts(cost_unit_id) {
|
||||
async function exportPayouts(costUnitId) {
|
||||
showLoading.value = true;
|
||||
|
||||
|
||||
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||
const data = await response.json();
|
||||
const exportUrl = '/api/v1/cost-unit/' + costUnitId + '/export-payouts';
|
||||
|
||||
try {
|
||||
if (_mareike_download_as_zip()) {
|
||||
const response = await fetch("/wp-json/mareike/costunits/export-payouts", {
|
||||
method: "POST",
|
||||
if (data.tenant.download_exports) {
|
||||
const response = await fetch(exportUrl, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
mareike_nonce: _mareike_nonce(),
|
||||
costunit: cost_unit_id,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = downloadUrl;
|
||||
a.download = `payouts-${cost_unit_id}.zip`;
|
||||
a.download = "Abrechnungen-Sippenstunden.zip";
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(a);
|
||||
}, 100);
|
||||
} else {
|
||||
await request("/wp-json/mareike/costunits/export-payouts", {
|
||||
method: "POST",
|
||||
body: {
|
||||
mareike_nonce: _mareike_nonce(),
|
||||
costunit: cost_unit_id,
|
||||
}
|
||||
const response = await request(exportUrl, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
toast.success(response.message);
|
||||
}
|
||||
showLoading.value = false;
|
||||
toast.success('Die Abrechnungen wurden exportiert.');
|
||||
|
||||
} catch (err) {
|
||||
showLoading.value = false;
|
||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||
@@ -176,7 +167,7 @@ async function export_payouts(cost_unit_id) {
|
||||
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
||||
<input v-else type="button" value="Abrechnungen einsehen" />
|
||||
<br />
|
||||
<input v-if="!costUnit.archived" type="button" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||
<input v-if="!costUnit.archived" type="button" @click="exportPayouts(costUnit.id)" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -217,17 +208,10 @@ async function export_payouts(cost_unit_id) {
|
||||
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div v-else-if="showInvoiceList">
|
||||
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
||||
|
||||
|
||||
<LoadingModal :show="showLoading" v-if="_mareike_use_webdav()" message="Die PDF-Dateien werden asynchron erzeugt, diese sollten in 10 Minuten auf dem Webdav-Server liegen', 'mareike')" />
|
||||
<LoadingModal :show="showLoading" v-else message='Die Abrechnungen werden exportiert, bitte warten.' />
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
@@ -236,7 +220,7 @@ async function export_payouts(cost_unit_id) {
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
|
||||
<LoadingModal :show="showLoading" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -23,7 +23,7 @@ const commonProps = reactive({
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/retreive-global-data');
|
||||
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||
const data = await response.json();
|
||||
Object.assign(commonProps, data);
|
||||
|
||||
@@ -63,8 +63,6 @@ async function updateCostUnit() {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(props.data.treasurers)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -74,8 +72,7 @@ console.log(props.data.treasurers)
|
||||
title="Schatzis zuweisen"
|
||||
@close="emit('closeTreasurers')"
|
||||
>
|
||||
Zuständige Schatzis:
|
||||
|
||||
<h3>Zuständige Schatzis:</h3>
|
||||
<p v-for="user in commonProps.activeUsers">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Actions\UpdatePersonalData;
|
||||
|
||||
use App\Repositories\UserRepository;
|
||||
|
||||
class UpdatePersonalDataCommand
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UpdatePersonalDataRequest $request,
|
||||
private readonly UserRepository $users
|
||||
) {}
|
||||
|
||||
public function execute(): UpdatePersonalDataResponse
|
||||
{
|
||||
$this->users->updatePersonalData($this->request);
|
||||
|
||||
$response = new UpdatePersonalDataResponse();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Actions\UpdatePersonalData;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UpdatePersonalDataRequest
|
||||
{
|
||||
public function __construct(
|
||||
public readonly User $user,
|
||||
public readonly ?string $nickname,
|
||||
public readonly ?string $email,
|
||||
public readonly ?string $phone,
|
||||
public readonly ?string $address1,
|
||||
public readonly ?string $address2,
|
||||
public readonly ?string $postcode,
|
||||
public readonly ?string $city,
|
||||
public readonly ?string $birthday,
|
||||
public readonly ?string $tetanusVaccination,
|
||||
public readonly ?string $medications,
|
||||
public readonly ?string $allergies,
|
||||
public readonly ?string $intolerances,
|
||||
public readonly ?string $eatingHabits,
|
||||
public readonly ?string $swimmingPermission,
|
||||
public readonly ?string $firstAidPermission,
|
||||
public readonly ?string $bankAccountOwner,
|
||||
public readonly ?string $bankAccountIban,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Actions\UpdatePersonalData;
|
||||
|
||||
class UpdatePersonalDataResponse
|
||||
{
|
||||
public bool $success;
|
||||
}
|
||||
@@ -7,8 +7,6 @@ use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Js;
|
||||
|
||||
class DashboardController extends CommonController {
|
||||
public function __invoke(Request $request) {
|
||||
@@ -21,7 +19,11 @@ class DashboardController extends CommonController {
|
||||
|
||||
private function renderForLoggedInUser(Request $request) {
|
||||
$authCheckProvider = new AuthCheckProvider;
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', ['appName' => app('tenant')->name]);
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', [
|
||||
'myInvoices' => $this->invoices->getMyInvoicesWidget(),
|
||||
'myParticipations' => $this->eventParticipants->getMyParticipations(),
|
||||
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
@@ -37,4 +39,12 @@ class DashboardController extends CommonController {
|
||||
$costUnits = $this->costUnits->listForSummary(5);
|
||||
return response()->json(['openCostUnits' => $costUnits]);
|
||||
}
|
||||
|
||||
public function getMyParticipations() : JsonResponse {
|
||||
return response()->json(['myParticipations' => $this->eventParticipants->getMyParticipations()]);
|
||||
}
|
||||
|
||||
public function getUpcomingEvents() : JsonResponse {
|
||||
return response()->json(['upcomingEvents' => $this->events->getUpcoming()]);
|
||||
}
|
||||
}
|
||||
|
||||
14
app/Domains/Dashboard/Controllers/MessagesController.php
Normal file
14
app/Domains/Dashboard/Controllers/MessagesController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
|
||||
class MessagesController extends CommonController {
|
||||
public function __invoke() {
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/Messages', []);
|
||||
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
45
app/Domains/Dashboard/Controllers/PersonalDataController.php
Normal file
45
app/Domains/Dashboard/Controllers/PersonalDataController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
|
||||
class PersonalDataController extends CommonController
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
if (!$this->checkAuth()) {
|
||||
return redirect()->intended('/login');
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$data = $this->users->getPersonalData($user);
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/PersonalData', [
|
||||
'personalData' => [
|
||||
'firstname' => $data['firstname'],
|
||||
'lastname' => $data['lastname'],
|
||||
'birthday' => $data['birthday'],
|
||||
'nickname' => $data['nickname'],
|
||||
'email' => $data['email'],
|
||||
'phone' => $data['phone'],
|
||||
'address1' => $data['address_1'],
|
||||
'address2' => $data['address_2'],
|
||||
'postcode' => $data['postcode'],
|
||||
'city' => $data['city'],
|
||||
'medications' => $data['medications'],
|
||||
'allergies' => $data['allergies'],
|
||||
'intolerances' => $data['intolerances'],
|
||||
'eatingHabits' => $data['eating_habits'],
|
||||
'swimmingPermission' => $data['swimming_permission'],
|
||||
'firstAidPermission' => $data['first_aid_permission'],
|
||||
'bankAccountOwner' => $data['bank_account_owner'],
|
||||
'bankAccountIban' => $data['bank_account_iban'],
|
||||
'tetanusVaccination' => $data['tetanus_vaccination'],
|
||||
],
|
||||
]);
|
||||
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Domains\Dashboard\Actions\UpdatePersonalData\UpdatePersonalDataCommand;
|
||||
use App\Domains\Dashboard\Actions\UpdatePersonalData\UpdatePersonalDataRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StorePersonalDataController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$actionRequest = new UpdatePersonalDataRequest(
|
||||
user: $user,
|
||||
nickname: $request->input('nickname'),
|
||||
email: $request->input('email'),
|
||||
phone: $request->input('phone'),
|
||||
address1: $request->input('address1'),
|
||||
address2: $request->input('address2'),
|
||||
postcode: $request->input('postcode'),
|
||||
city: $request->input('city'),
|
||||
medications: $request->input('medications'),
|
||||
allergies: $request->input('allergies'),
|
||||
intolerances: $request->input('intolerances'),
|
||||
eatingHabits: $request->input('eatingHabits'),
|
||||
swimmingPermission: $request->input('swimmingPermission'),
|
||||
firstAidPermission: $request->input('firstAidPermission'),
|
||||
bankAccountOwner: $request->input('bankAccountOwner'),
|
||||
bankAccountIban: $request->input('bankAccountIban'),
|
||||
birthday: $request->input('birthday'),
|
||||
tetanusVaccination: $request->input('tetanusVaccination'),
|
||||
);
|
||||
|
||||
$command = new UpdatePersonalDataCommand($actionRequest, $this->users);
|
||||
$command->execute();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Deine Daten wurden erfolgreich gespeichert.']);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||
use App\Domains\Dashboard\Controllers\StorePersonalDataController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -9,7 +10,12 @@ 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('/upcoming-events', [DashboardController::class, 'getUpcomingEvents']);
|
||||
Route::get('/my-participations', [DashboardController::class, 'getMyParticipations']);
|
||||
Route::post('/personal-data', StorePersonalDataController::class);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||
use App\Domains\Dashboard\Controllers\MessagesController;
|
||||
use App\Domains\Dashboard\Controllers\PersonalDataController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/personal-data', PersonalDataController::class);
|
||||
Route::get('/messages', MessagesController::class);
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {onMounted} from "vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import MyInvoices from "./Partials/Widgets/MyInvoices.vue";
|
||||
import MyParticipations from "./Partials/Widgets/MyParticipations.vue";
|
||||
|
||||
const props = defineProps({
|
||||
myInvoices: Object,
|
||||
myParticipations: Object,
|
||||
})
|
||||
|
||||
|
||||
function newInvoice() {
|
||||
window.location.href = '/invoice/new';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
Meine Abrechnungen
|
||||
<shadowed-box class="dashboard-widget-box" style="height: 275px;">
|
||||
<MyInvoices />
|
||||
<input type="button" value="Neue Abrechnung" @click="newInvoice" style="margin-top: 20px;">
|
||||
|
||||
|
||||
</shadowed-box>
|
||||
</diV>
|
||||
</AppLayout>
|
||||
@@ -31,7 +45,17 @@ import {toast} from "vue3-toastify";
|
||||
|
||||
.dashboard-widget-box {
|
||||
flex-grow: 1; display: inline-block;
|
||||
height: 150px;
|
||||
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>
|
||||
|
||||
25
app/Domains/Dashboard/Views/Messages.vue
Normal file
25
app/Domains/Dashboard/Views/Messages.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Meine Nachrichten'>
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
Diese Funktion steht aktuell nicht zur Verfügung.<br />
|
||||
Bitte versuche es später noch einmal.
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
@@ -16,8 +16,9 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Meine Abrechnungen</h2>
|
||||
<p v-for="invoice in myInvoices.myInvoices" class="widget-content-item">
|
||||
<a :href="'/invoices/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
||||
<a :href="'/invoice/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
||||
<label>
|
||||
{{invoice.amount}}
|
||||
</label>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<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);
|
||||
});
|
||||
|
||||
function navigateTo(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
</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="Jetzt anmelden" class="button" @click="navigateTo('/event/available-events')" />
|
||||
</p>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bg-red {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.bg-green {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.bg-yellow {
|
||||
background-color: #e4e44c;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<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);
|
||||
});
|
||||
|
||||
function navigateTo(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table v-if="myParticipations.myParticipations.length > 0">
|
||||
<tr v-for="participation in myParticipations.myParticipations.slice(0, 3)" class="widget-content-item">
|
||||
<td>
|
||||
{{participation.eventName}}<br />
|
||||
{{participation.event.location}},
|
||||
{{participation.arrivalDateReadable}} - {{participation.departureDateReadable}}
|
||||
</td>
|
||||
<td>
|
||||
<Icon name="euro-sign" style="padding: 2px; font-size: 10pt; color: #ffffff; margin-right: 5px;" :class="participation.needs_payment ? 'bg-red' : 'bg-green'" />
|
||||
<Icon name="award" style="padding: 2px; font-size: 10pt; color: #ffffff; margin-right: 5px;" :class="participation.cocColor" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else>
|
||||
Du bist aktuelle für keine Veranstaltung angemeldet.<br /><br />
|
||||
<input type="button" value="Jetzt anmelden" class="button" @click="navigateTo('/event/available-events')" />
|
||||
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bg-red {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.bg-green {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.bg-yellow {
|
||||
background-color: #e4e44c;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive} from "vue";
|
||||
|
||||
const events = reactive({
|
||||
'upcomingEvents': '',
|
||||
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/dashboard/upcoming-events');
|
||||
const data = await response.json();
|
||||
Object.assign(events, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="widget-content-item" v-if="events.upcomingEvents.length > 0">
|
||||
<tr>
|
||||
<td style="font-weight: bold">Veranstaltung</td>
|
||||
<td style="font-weight: bold">Teilis</td>
|
||||
<td style="font-weight: bold">Team</td>
|
||||
<td style="font-weight: bold">GruFüs</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="event in events.upcomingEvents">
|
||||
<td><a :href="'/event/details/' + event.identifier" class="link">{{event.nameShort}}</a></td>
|
||||
<td style="text-align: center;">{{event.countParticipant}}</td>
|
||||
<td style="text-align: center;">{{event.countTeam}}</td>
|
||||
<td style="text-align: center;">{{event.countVolunteer}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p v-else style="padding: 10px; font-weight: bold">Es existieren im Moment keine Veranstaltungen, für die du verantwortlich bist.</p>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
187
app/Domains/Dashboard/Views/PersonalData.vue
Normal file
187
app/Domains/Dashboard/Views/PersonalData.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { request } from '../../../../resources/js/components/HttpClient.js'
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import IbanInput from "../../../Views/Components/IbanInput.vue";
|
||||
|
||||
const props = defineProps({
|
||||
personalData: Object,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
nickname: props.personalData.nickname ?? '',
|
||||
email: props.personalData.email ?? '',
|
||||
phone: props.personalData.phone ?? '',
|
||||
address1: props.personalData.address1 ?? '',
|
||||
address2: props.personalData.address2 ?? '',
|
||||
postcode: props.personalData.postcode ?? '',
|
||||
city: props.personalData.city ?? '',
|
||||
birthday: props.personalData.birthday ?? '',
|
||||
tetanusVaccination: props.personalData.tetanusVaccination ?? '',
|
||||
medications: props.personalData.medications ?? '',
|
||||
allergies: props.personalData.allergies ?? '',
|
||||
intolerances: props.personalData.intolerances ?? '',
|
||||
eatingHabits: props.personalData.eatingHabits ?? '',
|
||||
swimmingPermission: props.personalData.swimmingPermission ?? '',
|
||||
firstAidPermission: props.personalData.firstAidPermission ?? '',
|
||||
bankAccountOwner: props.personalData.bankAccountOwner ?? '',
|
||||
bankAccountIban: props.personalData.bankAccountIban ?? '',
|
||||
})
|
||||
|
||||
const saving = ref(false)
|
||||
const successMessage = ref('')
|
||||
const errorMessage = ref('')
|
||||
|
||||
const submit = async () => {
|
||||
saving.value = true
|
||||
successMessage.value = ''
|
||||
errorMessage.value = ''
|
||||
|
||||
const result = await request('/api/v1/dashboard/personal-data', {
|
||||
method: 'POST',
|
||||
body: { ...form },
|
||||
})
|
||||
|
||||
saving.value = false
|
||||
|
||||
if (result?.success) {
|
||||
toast.success(result.message)
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Persönliche Daten'>
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
|
||||
<div class="max-w-2xl mx-auto p-6">
|
||||
<form @submit.prevent="submit">
|
||||
<table class="form-table" style="width: 90%; margin: 10px;">
|
||||
|
||||
<!-- Nicht veränderbare Felder -->
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Vorname:</td>
|
||||
<td><span class="text-gray-700">{{ personalData.firstname }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Nachname:</td>
|
||||
<td><span class="text-gray-700">{{ personalData.lastname }}</span></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Veränderbare Felder -->
|
||||
<tr>
|
||||
<td>Pfadiname:</td>
|
||||
<td><input type="text" v-model="form.nickname" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E-Mail:</td>
|
||||
<td><input type="email" v-model="form.email" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telefon:</td>
|
||||
<td><input type="text" v-model="form.phone" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Straße / Hausnummer:</td>
|
||||
<td><input type="text" v-model="form.address1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Adresszusatz:</td>
|
||||
<td><input type="text" v-model="form.address2" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PLZ:</td>
|
||||
<td><input type="text" v-model="form.postcode" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ort:</td>
|
||||
<td><input type="text" v-model="form.city" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Geburtsdatum:</td>
|
||||
<td><input type="date" v-model="form.birthday" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Medikamente:</td>
|
||||
<td><input type="text" v-model="form.medications" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allergien:</td>
|
||||
<td><input type="text" v-model="form.allergies" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unverträglichkeiten:</td>
|
||||
<td><input type="text" v-model="form.intolerances" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Letzte Tetanus-Impfung:</td>
|
||||
<td><input type="date" v-model="form.tetanusVaccination" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ernährungsgewohnheiten:</td>
|
||||
<td>
|
||||
<select v-model="form.eatingHabits">
|
||||
<option value="EATING_HABIT_VEGAN">Vegan</option>
|
||||
<option value="EATING_HABIT_VEGETARIAN">Vegetarisch</option>
|
||||
<option value="EATING_HABIT_OMNIVOR">Omnivor</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Badeerlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="form.swimmingPermission">
|
||||
<option value="SWIMMING_PERMISSION_ALLOWED">Erteilt, kann schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_LIMITED">Erteilt, kann nicht schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_DENIED">Nicht erteilt</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Erste-Hilfe-Erlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="form.firstAidPermission">
|
||||
<option value="FIRST_AID_PERMISSION_ALLOWED">Erweiterte Erste Hilfe erlaubt</option>
|
||||
<option value="FIRST_AID_PERMISSION_DENIED">Erweiterte Erste Hilfe verweigert</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kontoinhaber*in:</td>
|
||||
<td><input type="text" v-model="form.bankAccountOwner" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IBAN:</td>
|
||||
<td><IbanInput v-model="form.bankAccountIban" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" class="btn-row" style="padding-top: 20px;">
|
||||
<button type="submit" class="button" :disabled="saving">
|
||||
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CertificateOfConductionCheckCommand {
|
||||
function __construct(public CertificateOfConductionCheckRequest $request)
|
||||
{}
|
||||
|
||||
public function execute() : CertificateOfConductionCheckResponse {
|
||||
$response = new CertificateOfConductionCheckResponse();
|
||||
|
||||
$localGroup = str_replace('Stamm ', '', $this->request->participant->localGroup()->first()->name);
|
||||
|
||||
$apiResponse = Http::acceptJson()
|
||||
->asJson()
|
||||
->withoutVerifying()
|
||||
->post(env('COC_CHECK_URL'), [
|
||||
'firstName' => trim($this->request->participant->firstname),
|
||||
'lastName' => $this->request->participant->lastname,
|
||||
'nickname' => $this->request->participant->nickname,
|
||||
'address' => trim($this->request->participant->address_1 . ' ' . $this->request->participant->address_2),
|
||||
'zip' => $this->request->participant->zip,
|
||||
'city' => $this->request->participant->city,
|
||||
'birthday' => $this->request->participant->birthday->format('Y-m-d'),
|
||||
'email' => $this->request->participant->email_1,
|
||||
'localGroup' => $localGroup,
|
||||
'checkForDate' => $this->request->participant->departure_date->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
if (! $apiResponse->successful()) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$responseParsed = $apiResponse->json();
|
||||
|
||||
if (($responseParsed['status'] ?? null) !== '200/ok') {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->status = match ($responseParsed['efzStatus']) {
|
||||
'NOT_REQUIRED' => EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||
'CHECKED_VALID' => EfzStatus::EFZ_STATUS_CHECKED_VALID,
|
||||
'CHECKED_INVALID' => EfzStatus::EFZ_STATUS_CHECKED_INVALID,
|
||||
default => EfzStatus::EFZ_STATUS_NOT_CHECKED
|
||||
};
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class CertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
|
||||
class CertificateOfConductionCheckResponse {
|
||||
public string $status;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->status = EfzStatus::EFZ_STATUS_NOT_CHECKED;
|
||||
}
|
||||
}
|
||||
76
app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
Normal file
76
app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Models\Event;
|
||||
use App\Models\Tenant;
|
||||
use App\RelationModels\EventEatingHabits;
|
||||
use App\RelationModels\EventLocalGroups;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateEventCommand {
|
||||
private CreateEventRequest $request;
|
||||
public function __construct(CreateEventRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
public function execute(): CreateEventResponse {
|
||||
$response = new CreateEventResponse();
|
||||
|
||||
|
||||
|
||||
$prefix = $this->request->begin->format('Y-m_');
|
||||
if (!str_starts_with($this->request->name, $prefix)) {
|
||||
$this->request->name = $prefix . $this->request->name;
|
||||
}
|
||||
|
||||
|
||||
$event = Event::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'name' => $this->request->name,
|
||||
'identifier' => Str::random(10),
|
||||
'location' => $this->request->location,
|
||||
'postal_code' => $this->request->postalCode,
|
||||
'email' => $this->request->email,
|
||||
'start_date' => $this->request->begin,
|
||||
'end_date' => $this->request->end,
|
||||
'early_bird_end' => $this->request->earlyBirdEnd,
|
||||
'registration_final_end' => $this->request->registrationFinalEnd,
|
||||
'early_bird_end_amount_increase' => $this->request->earlyBirdEndAmountIncrease,
|
||||
'account_owner' => $this->request->accountOwner,
|
||||
'account_iban' => $this->request->accountIban,
|
||||
'participation_fee_type' => $this->request->participationFeeType->slug,
|
||||
'pay_per_day' => $this->request->payPerDay,
|
||||
'pay_direct' => $this->request->payDirect,
|
||||
'total_max_amount' => 0,
|
||||
'support_per_person' => 0,
|
||||
'support_flat' => 0,
|
||||
]);
|
||||
|
||||
if ($event !== null) {
|
||||
EventEatingHabits::create([
|
||||
'event_id' => $event->id,
|
||||
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGAN)->first()->id,
|
||||
]);
|
||||
|
||||
EventEatingHabits::create([
|
||||
'event_id' => $event->id,
|
||||
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGETARIAN)->first()->id,
|
||||
]);
|
||||
|
||||
if (app('tenant')->slug === 'lv') {
|
||||
foreach(Tenant::where(['is_active_local_group' => true])->get() as $tenant) {
|
||||
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => $tenant->id]);
|
||||
}
|
||||
} else {
|
||||
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => app('tenant')->id]);
|
||||
}
|
||||
|
||||
|
||||
$response->success = true;
|
||||
$response->event = $event;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
40
app/Domains/Event/Actions/CreateEvent/CreateEventRequest.php
Normal file
40
app/Domains/Event/Actions/CreateEvent/CreateEventRequest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use DateTime;
|
||||
|
||||
class CreateEventRequest {
|
||||
public string $name;
|
||||
public string $location;
|
||||
public string $postalCode;
|
||||
public string $email;
|
||||
public DateTime $begin;
|
||||
public DateTime $end;
|
||||
public DateTime $earlyBirdEnd;
|
||||
public DateTime $registrationFinalEnd;
|
||||
public int $earlyBirdEndAmountIncrease;
|
||||
public ParticipationFeeType $participationFeeType;
|
||||
public string $accountOwner;
|
||||
public string $accountIban;
|
||||
public bool $payPerDay;
|
||||
public bool $payDirect;
|
||||
|
||||
public function __construct(string $name, string $location, string $postalCode, string $email, DateTime $begin, DateTime $end, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $earlyBirdEndAmountIncrease, ParticipationFeeType $participationFeeType, string $accountOwner, string $accountIban, bool $payPerDay, bool $payDirect) {
|
||||
$this->name = $name;
|
||||
$this->location = $location;
|
||||
$this->postalCode = $postalCode;
|
||||
$this->email = $email;
|
||||
$this->begin = $begin;
|
||||
$this->end = $end;
|
||||
$this->earlyBirdEnd = $earlyBirdEnd;
|
||||
$this->registrationFinalEnd = $registrationFinalEnd;
|
||||
$this->earlyBirdEndAmountIncrease = $earlyBirdEndAmountIncrease;
|
||||
$this->participationFeeType = $participationFeeType;
|
||||
$this->accountOwner = $accountOwner;
|
||||
$this->accountIban = $accountIban;
|
||||
$this->payPerDay = $payPerDay;
|
||||
$this->payDirect = $payDirect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class CreateEventResponse {
|
||||
public bool $success;
|
||||
public ?Event $event;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->event = null;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ManualCertificateOfConductionCheckCommand {
|
||||
function __construct(public ManualCertificateOfConductionCheckRequest $request)
|
||||
{}
|
||||
|
||||
public function execute() : ManualCertificateOfConductionCheckResponse {
|
||||
$response = new ManualCertificateOfConductionCheckResponse();
|
||||
|
||||
|
||||
$this->request->participant->efz_status = EfzStatus::EFZ_STATUS_CHECKED_VALID;
|
||||
$this->request->participant->save();
|
||||
$response->success = true;
|
||||
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantCocCompleteMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class ManualCertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
|
||||
class ManualCertificateOfConductionCheckResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ParticipantPayment;
|
||||
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentOverpaidMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentPaidMail;
|
||||
use App\Providers\MissingPaymentProvider;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ParticipantPaymentCommand {
|
||||
public function __construct(public ParticipantPaymentRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() : ParticipantPaymentResponse {
|
||||
$response = new ParticipantPaymentResponse();
|
||||
|
||||
$this->request->participant->amount_paid = $this->request->amountPaid;
|
||||
$this->request->participant->save();
|
||||
|
||||
$response->amountPaid = $this->request->participant->amount_paid;
|
||||
$response->amountExpected = $this->request->participant->amount;
|
||||
|
||||
$amountToPay = MissingPaymentProvider::calculateMissingPayment(
|
||||
amountPaid: $this->request->participant->amount_paid,
|
||||
amountToPay: $this->request->participant->amount,
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case $amountToPay->getAmount() > 0:
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
case $amountToPay->getAmount() < 0:
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$response->participant = $this->request->participant;
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ParticipantPayment;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class ParticipantPaymentRequest {
|
||||
public EventParticipant $participant;
|
||||
public Amount $amountPaid;
|
||||
|
||||
public function __construct(EventParticipant $participant, Amount $amountPaid) {
|
||||
$this->participant = $participant;
|
||||
$this->amountPaid = $amountPaid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ParticipantPayment;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class ParticipantPaymentResponse {
|
||||
public bool $success;
|
||||
public ?Amount $amountPaid;
|
||||
public ?Amount $amountExpected;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->amountPaid = null;
|
||||
$this->amountExpected = null;
|
||||
$this->success = false;
|
||||
$this->participant = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendMissingPaymentMailsCommand {
|
||||
function __construct(public SendMissingPaymentMailsRequest $request) {}
|
||||
|
||||
public function execute() : SendMissingPaymentMailsResponse {
|
||||
$response = new SendMissingPaymentMailsResponse();
|
||||
|
||||
foreach ($this->request->eventParticipants->getParticipantsWithMissingPayments($this->request->event, $this->request->httpRequest) as $participant) {
|
||||
$participantResource = $participant->toResource()->toArray($this->request->httpRequest);
|
||||
if (!$participantResource['needs_payment']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null && $participant->email_2 !== $participant->email_1) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
|
||||
$response->remindedParticipants++;
|
||||
}
|
||||
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Repositories\EventParticipantRepository;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SendMissingPaymentMailsRequest {
|
||||
function __construct(
|
||||
public Event $event,
|
||||
public EventParticipantRepository $eventParticipants,
|
||||
public Request $httpRequest
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||
|
||||
class SendMissingPaymentMailsResponse {
|
||||
function __construct(
|
||||
public bool $success = false,
|
||||
public int $remindedParticipants = 0
|
||||
) {}
|
||||
}
|
||||
18
app/Domains/Event/Actions/SetCostUnit/SetCostUnitCommand.php
Normal file
18
app/Domains/Event/Actions/SetCostUnit/SetCostUnitCommand.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||
|
||||
class SetCostUnitCommand {
|
||||
private SetCostUnitRequest $request;
|
||||
|
||||
public function __construct(SetCostUnitRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : SetCostUnitResponse {
|
||||
$response = new SetCostUnitResponse();
|
||||
$this->request->event->cost_unit_id = $this->request->costUnit->id;
|
||||
$response->success = $this->request->event->save();
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
16
app/Domains/Event/Actions/SetCostUnit/SetCostUnitRequest.php
Normal file
16
app/Domains/Event/Actions/SetCostUnit/SetCostUnitRequest.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
use App\Models\Event;
|
||||
|
||||
class SetCostUnitRequest {
|
||||
public Event $event;
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(Event $event, CostUnit $costUnit) {
|
||||
$this->event = $event;
|
||||
$this->costUnit = $costUnit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||
|
||||
class SetCostUnitResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||
|
||||
use App\RelationModels\EventParticipationFee;
|
||||
|
||||
class SetParticipationFeesCommand {
|
||||
private SetParticipationFeesRequest $request;
|
||||
public function __construct(SetParticipationFeesRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : SetParticipationFeesResponse {
|
||||
$response = new SetParticipationFeesResponse();
|
||||
$this->request->event->sibling_reduction = $this->request->siblingReduction;
|
||||
$this->request->event->save();
|
||||
|
||||
$this->cleanBefore();
|
||||
|
||||
$this->request->event->participationFee1()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeFirst['type'],
|
||||
'name' => $this->request->participationFeeFirst['name'],
|
||||
'description' => $this->request->participationFeeFirst['description'],
|
||||
'amount_standard' => $this->request->participationFeeFirst['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeFirst['amount_reduced'] ? $this->request->participationFeeFirst['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeFirst['amount_solidarity'] ? $this->request->participationFeeFirst['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
|
||||
if ($this->request->participationFeeSecond !== null) {
|
||||
$this->request->event->participationFee2()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeSecond['type'],
|
||||
'name' => $this->request->participationFeeSecond['name'],
|
||||
'description' => $this->request->participationFeeSecond['description'],
|
||||
'amount_standard' => $this->request->participationFeeSecond['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeSecond['amount_reduced'] ? $this->request->participationFeeSecond['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeSecond['amount_solidarity'] ? $this->request->participationFeeSecond['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
if ($this->request->participationFeeThird !== null) {
|
||||
$this->request->event->participationFee3()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeThird['type'],
|
||||
'name' => $this->request->participationFeeThird['name'],
|
||||
'description' => $this->request->participationFeeThird['description'],
|
||||
'amount_standard' => $this->request->participationFeeThird['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeThird['amount_reduced'] ? $this->request->participationFeeThird['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeThird['amount_solidarity'] ? $this->request->participationFeeThird['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
if ($this->request->participationFeeFourth !== null) {
|
||||
$this->request->event->participationFee4()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeFourth['type'],
|
||||
'name' => $this->request->participationFeeFourth['name'],
|
||||
'description' => $this->request->participationFeeFourth['description'],
|
||||
'amount_standard' => $this->request->participationFeeFourth['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeFourth['amount_reduced'] ? $this->request->participationFeeFourth['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeFourth['amount_solidarity'] ? $this->request->participationFeeFourth['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function cleanBefore() {
|
||||
if ($this->request->event->participationFee1()->first() !== null) {
|
||||
$this->request->event->participationFee1()->first()->delete();
|
||||
}
|
||||
|
||||
if ($this->request->event->participationFee2()->first() !== null) {
|
||||
$this->request->event->participationFee2()->first()->delete();
|
||||
}
|
||||
|
||||
if ($this->request->event->participationFee3()->first() !== null) {
|
||||
$this->request->event->participationFee3()->first()->delete();
|
||||
}
|
||||
|
||||
if ($this->request->event->participationFee4()->first() !== null) {
|
||||
$this->request->event->participationFee4()->first()->delete();
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\RelationModels\EventParticipationFee;
|
||||
|
||||
class SetParticipationFeesRequest {
|
||||
public Event $event;
|
||||
public array $participationFeeFirst;
|
||||
public ?array $participationFeeSecond;
|
||||
public ?array $participationFeeThird;
|
||||
public ?array $participationFeeFourth;
|
||||
|
||||
public bool $siblingReduction;
|
||||
|
||||
|
||||
public function __construct(Event $event, array $participationFeeFirst, bool $siblingReduction) {
|
||||
$this->event = $event;
|
||||
$this->participationFeeFirst = $participationFeeFirst;
|
||||
$this->participationFeeSecond = null;
|
||||
$this->participationFeeThird = null;
|
||||
$this->participationFeeFourth = null;
|
||||
$this->siblingReduction = $siblingReduction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||
|
||||
class SetParticipationFeesResponse {
|
||||
public bool $success;
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
||||
use App\Mail\ParticipantParticipationMails\ParticipantSignOffMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SetParticipationStateCommand {
|
||||
function __construct(private SetParticipationStateSignoffRequest|SetParticipationStateReSignonRequest $request) {}
|
||||
|
||||
public function execute() : SetParticipationStateResponse {
|
||||
$response = new SetParticipationStateResponse();
|
||||
|
||||
|
||||
switch (true) {
|
||||
case $this->request instanceof SetParticipationStateSignoffRequest:
|
||||
$this->request->participant->unregistered_at = $this->request->date;
|
||||
$this->request->participant->save();
|
||||
$response->success = true;
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantSignOffMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantSignOffMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
|
||||
break;
|
||||
case $this->request instanceof SetParticipationStateReSignonRequest:
|
||||
$this->request->participant->unregistered_at = null;
|
||||
$this->request->participant->save();
|
||||
|
||||
Mail::to($this->request->participant->email_1)->send(new EventSignUpSuccessfullMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new EventSignUpSuccessfullMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SetParticipationStateReSignonRequest {
|
||||
function __construct(public EventParticipant $participant) {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
class SetParticipationStateResponse {
|
||||
function __construct(public bool $success = false) {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SetParticipationStateSignoffRequest {
|
||||
function __construct(public EventParticipant $participant, public \DateTime $date) {
|
||||
}
|
||||
}
|
||||
71
app/Domains/Event/Actions/SignUp/SignUpCommand.php
Normal file
71
app/Domains/Event/Actions/SignUp/SignUpCommand.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\ValueObjects\Age;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SignUpCommand {
|
||||
public function __construct(public SignUpRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() : SignUpResponse {
|
||||
$response = new SignUpResponse();
|
||||
|
||||
$eatingHabit = match ($this->request->eating_habit) {
|
||||
'vegan' => EatingHabit::EATING_HABIT_VEGAN,
|
||||
'vegetarian' => EatingHabit::EATING_HABIT_VEGETARIAN,
|
||||
default => EatingHabit::EATING_HABIT_OMNIVOR,
|
||||
};
|
||||
|
||||
$participantAge = new Age($this->request->birthday);
|
||||
$response->participant = $this->request->event->participants()->create(
|
||||
[
|
||||
'tenant' => $this->request->event->tenant,
|
||||
'user_id' => $this->request->user_id,
|
||||
'identifier' => Str::random(10),
|
||||
'firstname' => $this->request->firstname,
|
||||
'lastname' => $this->request->lastname,
|
||||
'nickname' => $this->request->nickname,
|
||||
'participation_type' => $this->request->participationType,
|
||||
'local_group' => $this->request->localGroup->slug,
|
||||
'birthday' => $this->request->birthday,
|
||||
'address_1' => $this->request->address_1,
|
||||
'address_2' => $this->request->address_2,
|
||||
'postcode' => $this->request->postcode,
|
||||
'city' => $this->request->city,
|
||||
'email_1' => $this->request->email_1,
|
||||
'email_2' => $this->request->email_2,
|
||||
'phone_1' => $this->request->phone_1,
|
||||
'phone_2' => $this->request->phone_2,
|
||||
'contact_person' => $this->request->contact_person,
|
||||
'allergies' => $this->request->allergies,
|
||||
'intolerances' => $this->request->intolerances,
|
||||
'medications' => $this->request->medications,
|
||||
'tetanus_vaccination' => $this->request->tetanus_vaccination,
|
||||
'eating_habit' => $eatingHabit,
|
||||
'swimming_permission' => $participantAge->isfullAged() ? 'SWIMMING_PERMISSION_ALLOWED' : $this->request->swimming_permission,
|
||||
'first_aid_permission' => $participantAge->isfullAged() ? 'FIRST_AID_PERMISSION_ALLOWED' : $this->request->first_aid_permission,
|
||||
'foto_socialmedia' => $this->request->foto_socialmedia,
|
||||
'foto_print' => $this->request->foto_print,
|
||||
'foto_webseite' => $this->request->foto_webseite,
|
||||
'foto_partner' => $this->request->foto_partner,
|
||||
'foto_intern' => $this->request->foto_intern,
|
||||
'arrival_date' => $this->request->arrival,
|
||||
'departure_date' => $this->request->departure,
|
||||
'arrival_eating' => $this->request->arrival_eating,
|
||||
'departure_eating' => $this->request->departure_eating,
|
||||
'notes' => $this->request->notes,
|
||||
'amount' => $this->request->amount,
|
||||
'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname,
|
||||
'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||
]
|
||||
);
|
||||
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
50
app/Domains/Event/Actions/SignUp/SignUpRequest.php
Normal file
50
app/Domains/Event/Actions/SignUp/SignUpRequest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\Event;
|
||||
use App\Models\Tenant;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class SignUpRequest {
|
||||
function __construct(
|
||||
public Event $event,
|
||||
public ?int $user_id,
|
||||
public string $firstname,
|
||||
public string $lastname,
|
||||
public ?string $nickname,
|
||||
public string $participationType,
|
||||
public Tenant $localGroup,
|
||||
public DateTime $birthday,
|
||||
public string $address_1,
|
||||
public string $address_2,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
public string $email_1,
|
||||
public ?string $phone_1,
|
||||
public ?string $email_2,
|
||||
public ?string $phone_2,
|
||||
public ?string $contact_person,
|
||||
public ?string $allergies,
|
||||
public ?string $intolerances,
|
||||
public ?string $medications,
|
||||
public ?DateTime $tetanus_vaccination,
|
||||
public string $eating_habit,
|
||||
public ?string $swimming_permission,
|
||||
public ?string $first_aid_permission,
|
||||
public bool $foto_socialmedia,
|
||||
public bool $foto_print,
|
||||
public bool $foto_webseite,
|
||||
public bool $foto_partner,
|
||||
public bool $foto_intern,
|
||||
public DateTime $arrival,
|
||||
public DateTime $departure,
|
||||
public int $arrival_eating,
|
||||
public int $departure_eating,
|
||||
public ?string $notes,
|
||||
public Amount $amount
|
||||
) {
|
||||
}
|
||||
}
|
||||
16
app/Domains/Event/Actions/SignUp/SignUpResponse.php
Normal file
16
app/Domains/Event/Actions/SignUp/SignUpResponse.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SignUpResponse {
|
||||
public bool $success;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->participant = null;
|
||||
}
|
||||
|
||||
}
|
||||
43
app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php
Normal file
43
app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
class UpdateEventCommand {
|
||||
public UpdateEventRequest $request;
|
||||
public function __construct(UpdateEventRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UpdateEventResponse {
|
||||
$response = new UpdateEventResponse();
|
||||
|
||||
$this->request->event->name = $this->request->eventName;
|
||||
$this->request->event->location = $this->request->eventLocation;
|
||||
$this->request->event->postal_code = $this->request->postalCode;
|
||||
$this->request->event->email = $this->request->email;
|
||||
$this->request->event->early_bird_end = $this->request->earlyBirdEnd;
|
||||
$this->request->event->registration_final_end = $this->request->registrationFinalEnd;
|
||||
$this->request->event->alcoholics_age = $this->request->alcoholicsAge;
|
||||
$this->request->event->support_per_person = $this->request->supportPerPerson;
|
||||
$this->request->event->support_flat = $this->request->flatSupport;
|
||||
$this->request->event->send_weekly_report = $this->request->sendWeeklyReports;
|
||||
$this->request->event->registration_allowed = $this->request->registrationAllowed;
|
||||
$this->request->event->save();
|
||||
|
||||
$this->request->event->resetAllowedEatingHabits();
|
||||
$this->request->event->resetContributingLocalGroups();
|
||||
|
||||
foreach($this->request->eatingHabits as $eatingHabit) {
|
||||
$this->request->event->eatingHabits()->attach($eatingHabit);
|
||||
}
|
||||
|
||||
foreach($this->request->contributingLocalGroups as $contributingLocalGroup) {
|
||||
$this->request->event->localGroups()->attach($contributingLocalGroup);
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php
Normal file
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class UpdateEventRequest {
|
||||
public Event $event;
|
||||
public string $eventName;
|
||||
public string $eventLocation;
|
||||
public string $postalCode;
|
||||
public string $email;
|
||||
public DateTime $earlyBirdEnd;
|
||||
public DateTime $registrationFinalEnd;
|
||||
public int $alcoholicsAge;
|
||||
public bool $sendWeeklyReports;
|
||||
public bool $registrationAllowed;
|
||||
public Amount $flatSupport;
|
||||
public Amount $supportPerPerson;
|
||||
public array $contributingLocalGroups;
|
||||
public array $eatingHabits;
|
||||
|
||||
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits) {
|
||||
$this->event = $event;
|
||||
$this->eventName = $eventName;
|
||||
$this->eventLocation = $eventLocation;
|
||||
$this->postalCode = $postalCode;
|
||||
$this->email = $email;
|
||||
$this->earlyBirdEnd = $earlyBirdEnd;
|
||||
$this->registrationFinalEnd = $registrationFinalEnd;
|
||||
$this->alcoholicsAge = $alcoholicsAge;
|
||||
$this->sendWeeklyReports = $sendWeeklyReports;
|
||||
$this->registrationAllowed = $registrationAllowed;
|
||||
$this->flatSupport = $flatSupport;
|
||||
$this->supportPerPerson = $supportPerPerson;
|
||||
$this->contributingLocalGroups = $contributingLocalGroups;
|
||||
$this->eatingHabits = $eatingHabits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
class UpdateEventResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||
|
||||
class UpdateManagersCommand {
|
||||
private UpdateManagersRequest $request;
|
||||
|
||||
public function __construct(UpdateManagersRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UpdateManagersResponse {
|
||||
$response = new UpdateManagersResponse();
|
||||
$this->request->event->resetMangers();
|
||||
|
||||
foreach ($this->request->managers as $manager) {
|
||||
$this->request->event->eventManagers()->attach($manager);
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class UpdateManagersRequest {
|
||||
public Event $event;
|
||||
public array $managers;
|
||||
|
||||
public function __construct(Event $event, array $managers) {
|
||||
$this->managers = $managers;
|
||||
$this->event = $event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||
|
||||
class UpdateManagersResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateParticipant;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentOverpaidMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentPaidMail;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Providers\MissingPaymentProvider;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class UpdateParticipantCommand {
|
||||
private UpdateParticipantResponse $response;
|
||||
function __construct(public UpdateParticipantRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$this->response = new UpdateParticipantResponse();
|
||||
|
||||
$p = clone($this->request->participant);
|
||||
|
||||
$p->firstname = $this->request->firstname;
|
||||
$p->lastname = $this->request->lastname;
|
||||
$p->nickname = $this->request->nickname;
|
||||
$p->address_1 = $this->request->address_1;
|
||||
$p->address_2 = $this->request->address_2;
|
||||
$p->postcode = $this->request->postcode;
|
||||
$p->city = $this->request->city;
|
||||
$p->local_group = $this->request->localgroup;
|
||||
$p->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday);
|
||||
$p->email_1 = $this->request->email_1;
|
||||
$p->phone_1 = $this->request->phone_1;
|
||||
$p->contact_person = $this->request->contact_person;
|
||||
$p->email_2 = $this->request->email_2;
|
||||
$p->phone_2 = $this->request->phone_2;
|
||||
$p->arrival_date = DateTime::createFromFormat('Y-m-d', $this->request->arrival);
|
||||
$p->departure_date = DateTime::createFromFormat('Y-m-d', $this->request->departure);
|
||||
$p->participation_type = $this->request->participationType;
|
||||
$p->eating_habit = $this->request->eatingHabit;
|
||||
$p->allergies = $this->request->allergies;
|
||||
$p->intolerances = $this->request->intolerances;
|
||||
$p->medications = $this->request->medications;
|
||||
$p->first_aid_permission = $this->request->extendedFirstAid;
|
||||
$p->swimming_permission = $this->request->swimmingPermission;
|
||||
$p->tetanus_vaccination = $this->request->tetanusVaccination !== null
|
||||
? DateTime::createFromFormat('Y-m-d', $this->request->tetanusVaccination)
|
||||
: null;
|
||||
$p->notes = $this->request->notes;
|
||||
$p->amount_paid = Amount::fromString($this->request->amountPaid);
|
||||
$p->amount = Amount::fromString($this->request->amountExpected);
|
||||
$p->efz_status = $this->request->cocStatus;
|
||||
|
||||
if (
|
||||
MissingPaymentProvider::calculateMissingPayment(amountPaid: $p->amount_paid, amountToPay: $p->amount)->getAmount()
|
||||
!==
|
||||
MissingPaymentProvider::calculateMissingPayment(amountPaid: $this->request->participant->amount_paid, amountToPay: $this->request->participant->amount)->getAmount()
|
||||
) {
|
||||
$this->handleAmountChanges($p);
|
||||
}
|
||||
|
||||
if (
|
||||
$p->efz_status !== $this->request->participant->efz_status
|
||||
) {
|
||||
$this->handleCocStatusChange($p);
|
||||
}
|
||||
|
||||
|
||||
$p->save();
|
||||
$this->response->success = true;
|
||||
$this->response->participant = $p;
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
private function handleAmountChanges(EventParticipant $participant) {
|
||||
$this->response->amountPaid = $participant->amount_paid;
|
||||
$this->response->amountExpected = $participant->amount;
|
||||
|
||||
$amountToPay = MissingPaymentProvider::calculateMissingPayment(
|
||||
amountPaid: $participant->amount_paid,
|
||||
amountToPay: $participant->amount,
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case $amountToPay->getAmount() > 0:
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
case $amountToPay->getAmount() < 0:
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleCocStatusChange(EventParticipant $participant) {
|
||||
$this->response->cocStatus = $participant->efzStatus()->first();
|
||||
|
||||
switch ($participant->efzStatus()->first()->slug) {
|
||||
case EfzStatus::EFZ_STATUS_CHECKED_VALID:
|
||||
case EfzStatus::EFZ_STATUS_NOT_REQUIRED:
|
||||
Mail::to($participant->email_1)->send(new ParticipantCocCompleteMail(
|
||||
participant: $participant,
|
||||
));
|
||||
break;
|
||||
|
||||
case EfzStatus::EFZ_STATUS_CHECKED_INVALID:
|
||||
Mail::to($participant->email_1)->send(new ParticipantCocInvalidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateParticipant;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class UpdateParticipantRequest {
|
||||
function __construct(
|
||||
public EventParticipant $participant,
|
||||
public string $firstname,
|
||||
public string $lastname,
|
||||
public ?string $nickname,
|
||||
public string $address_1,
|
||||
public ?string $address_2,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
public string $localgroup,
|
||||
public string $birthday,
|
||||
public string $email_1,
|
||||
public string $phone_1,
|
||||
public string $contact_person,
|
||||
public ?string $email_2,
|
||||
public ?string $phone_2,
|
||||
public string $arrival,
|
||||
public string $departure,
|
||||
public string $participationType,
|
||||
public string $eatingHabit,
|
||||
public ?string $allergies,
|
||||
public ?string $intolerances,
|
||||
public ?string $medications,
|
||||
public string $extendedFirstAid,
|
||||
public string $swimmingPermission,
|
||||
public ?string $tetanusVaccination,
|
||||
public ?string $notes,
|
||||
public string $amountPaid,
|
||||
public string $amountExpected,
|
||||
public string $cocStatus,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateParticipant;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class UpdateParticipantResponse {
|
||||
public bool $success;
|
||||
public ?EfzStatus $cocStatus;
|
||||
public ?Amount $amountPaid;
|
||||
public ?Amount $amountExpected;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->cocStatus =null;
|
||||
$this->amountPaid = null;
|
||||
$this->amountExpected = null;
|
||||
$this->participant = null;
|
||||
}
|
||||
}
|
||||
22
app/Domains/Event/Controllers/AvailableEventsController.php
Normal file
22
app/Domains/Event/Controllers/AvailableEventsController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class AvailableEventsController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request) : Response {
|
||||
$events = [];
|
||||
foreach ($this->events->getAvailable(false) as $event) {
|
||||
$events[] = $event->toResource()->toArray($request);
|
||||
};
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Event/ListAvailable', ['events' => $events]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
100
app/Domains/Event/Controllers/CreateController.php
Normal file
100
app/Domains/Event/Controllers/CreateController.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitCommand;
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||
use App\Domains\Event\Actions\CreateEvent\CreateEventCommand;
|
||||
use App\Domains\Event\Actions\CreateEvent\CreateEventRequest;
|
||||
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitCommand;
|
||||
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\EventResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreateController extends CommonController {
|
||||
public function __invoke() {
|
||||
return new InertiaProvider('Event/Create', [
|
||||
'emailAddress' => auth()->user()->email,
|
||||
'eventAccount' => $this->tenant->account_name,
|
||||
'eventIban' => $this->tenant->account_iban,
|
||||
'eventPayPerDay' => $this->tenant->slug === 'lv' ? true : false,
|
||||
'participationFeeType' => $this->tenant->slug === 'lv' ?
|
||||
ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED :
|
||||
ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY,
|
||||
])->render();
|
||||
}
|
||||
|
||||
public function doCreate(Request $request) : JsonResponse {
|
||||
|
||||
$eventBegin = DateTime::createFromFormat('Y-m-d', $request->input('eventBegin'));
|
||||
$eventEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventEnd'));
|
||||
$eventEarlyBirdEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventEarlyBirdEnd'));
|
||||
$registrationFinalEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventRegistrationFinalEnd'));
|
||||
$participationFeeType = ParticipationFeeType::where('slug', $request->input('eventParticipationFeeType'))->first();
|
||||
$payPerDay = $request->input('eventPayPerDay');
|
||||
$payDirect = $request->input('eventPayDirectly');
|
||||
|
||||
$billingDeadline = $eventEnd->modify('+1 month');
|
||||
|
||||
$createRequest = new CreateEventRequest(
|
||||
$request->input('eventName'),
|
||||
$request->input('eventLocation'),
|
||||
$request->input('eventPostalCode'),
|
||||
$request->input('eventEmail'),
|
||||
$eventBegin,
|
||||
$eventEnd,
|
||||
$eventEarlyBirdEnd,
|
||||
$registrationFinalEnd,
|
||||
$request->input('eventEarlyBirdEndAmountIncrease'),
|
||||
$participationFeeType,
|
||||
$request->input('eventAccount'),
|
||||
$request->input('eventIban'),
|
||||
$payPerDay,
|
||||
$payDirect
|
||||
);
|
||||
|
||||
$wasSuccessful = false;
|
||||
|
||||
$createCommand = new CreateEventCommand($createRequest);
|
||||
$result = $createCommand->execute();
|
||||
if ($result->success) {
|
||||
$createCostUnitRequest = new CreateCostUnitRequest(
|
||||
$result->event->name,
|
||||
CostUnitType::COST_UNIT_TYPE_EVENT,
|
||||
Amount::fromString('0,25'),
|
||||
true,
|
||||
$billingDeadline
|
||||
);
|
||||
|
||||
$createCostUnitCommand = new CreateCostUnitCommand($createCostUnitRequest);
|
||||
$costUnitResponse = $createCostUnitCommand->execute();
|
||||
|
||||
if ($costUnitResponse->success) {
|
||||
$costUnitUpdateRequest = new SetCostUnitRequest($result->event, $costUnitResponse->costUnit);
|
||||
$costUnitUpdateCommand = new SetCostUnitCommand($costUnitUpdateRequest);
|
||||
$costUnitSetResponse = $costUnitUpdateCommand->execute();
|
||||
$wasSuccessful = $costUnitSetResponse->success;
|
||||
}
|
||||
}
|
||||
|
||||
if ($wasSuccessful) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'event' => new EventResource($costUnitUpdateRequest->event)->toArray($request)
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Veranstaltung konnte nicht angelegt werden.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
198
app/Domains/Event/Controllers/DetailsController.php
Normal file
198
app/Domains/Event/Controllers/DetailsController.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesCommand;
|
||||
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesRequest;
|
||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventCommand;
|
||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventRequest;
|
||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
|
||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Providers\PdfGenerateAndDownloadProvider;
|
||||
use App\Resources\EventResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use function Symfony\Component\String\b;
|
||||
|
||||
class DetailsController extends CommonController {
|
||||
public function __invoke(string $eventId) {
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
||||
}
|
||||
|
||||
public function summary(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
return response()->json(['event' => $event->toResource()->toArray($request)]);
|
||||
}
|
||||
|
||||
public function updateCommonSettings(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
|
||||
$earlyBirdEnd = \DateTime::createFromFormat('Y-m-d', $request->input('earlyBirdEnd'));
|
||||
$registrationFinalEnd = \DateTime::createFromFormat('Y-m-d', $request->input('registrationFinalEnd'));
|
||||
$flatSupport = Amount::fromString($request->input('flatSupport'));
|
||||
$supportPerPerson = Amount::fromString($request->input('supportPerson'));
|
||||
|
||||
$contributinLocalGroups = $request->input('contributingLocalGroups');
|
||||
$eatingHabits = $request->input('eatingHabits');
|
||||
|
||||
$eventUpdateRequest = new UpdateEventRequest(
|
||||
$event,
|
||||
$request->input('eventName'),
|
||||
$request->input('eventLocation'),
|
||||
$request->input('postalCode'),
|
||||
$request->input('email'),
|
||||
$earlyBirdEnd,
|
||||
$registrationFinalEnd,
|
||||
$request->input('alcoholicsAge'),
|
||||
$request->input('sendWeeklyReports'),
|
||||
$request->input('registrationAllowed'),
|
||||
$flatSupport,
|
||||
$supportPerPerson,
|
||||
$contributinLocalGroups,
|
||||
$eatingHabits
|
||||
);
|
||||
|
||||
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
||||
$response = $eventUpdateCommand->execute();
|
||||
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
public function updateEventManagers(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
|
||||
$updateEventManagersRequest = new UpdateManagersRequest($event, $request->input('selectedManagers'));
|
||||
$updateEventManagersCommand = new UpdateManagersCommand($updateEventManagersRequest);
|
||||
$response = $updateEventManagersCommand->execute();
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
public function updateParticipationFees(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
|
||||
$participationFeeFirst = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||
'name' => 'Teilnehmer',
|
||||
'description' => $request->input('pft_1_description'),
|
||||
'amount_standard' => Amount::fromString($request->input('pft_1_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_1_amount_reduced') ? Amount::fromString($request->input('pft_1_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_1_amount_solidarity') ? Amount::fromString($request->input('pft_1_amount_solidarity')) : null
|
||||
];
|
||||
|
||||
$siblingReduction = $request->input('sibling_reduction') ?? false;
|
||||
$participationFeeRequest = new SetParticipationFeesRequest($event, $participationFeeFirst, $siblingReduction);
|
||||
|
||||
if ($request->input('pft_2_active')) {
|
||||
$participationFeeRequest->participationFeeSecond = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_TEAM,
|
||||
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Kernteam' : 'Solidaritätsbeitrag',
|
||||
'description' => $request->input('pft_2_description'),
|
||||
'amount_standard' => Amount::fromString($request->input('pft_2_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_2_amount_reduced') ? Amount::fromString($request->input('pft_2_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_2_amount_solidarity') ? Amount::fromString($request->input('pft_2_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
if ($request->input('pft_3_active')) {
|
||||
$participationFeeRequest->participationFeeThird = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_VOLUNTEER,
|
||||
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Unterstützende' : 'Reduzierter Beitrag',
|
||||
'description' => $event->participation_fee_type !== ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY ? $request->input('pft_3_description') : 'Nach Verfügbarkeit',
|
||||
'amount_standard' => Amount::fromString($request->input('pft_3_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_3_amount_reduced') ? Amount::fromString($request->input('pft_3_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_3_amount_solidarity') ? Amount::fromString($request->input('pft_3_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
if ($request->input('pft_4_active') && $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED) {
|
||||
$participationFeeRequest->participationFeeFourth = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_OTHER,
|
||||
'name' => 'Sonstige',
|
||||
'description' => $request->input('pft_4_description'),
|
||||
'amount_standard' => Amount::fromString($request->input('pft_4_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_4_amount_reduced') ? Amount::fromString($request->input('pft_4_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_4_amount_solidarity') ? Amount::fromString($request->input('pft_4_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
$participationFeeCommand = new SetParticipationFeesCommand($participationFeeRequest);
|
||||
$response = $participationFeeCommand->execute();
|
||||
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
public function downloadPdfList(string $eventId, string $listType, Request $request): Response
|
||||
{
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
|
||||
$participants = $this->eventParticipants->getForList($event, $request);
|
||||
$kitchenOverview = $this->eventParticipants->getKitchenOverview($event);
|
||||
$html = view('pdfs.' . $listType, [
|
||||
'event' => $event->name,
|
||||
'eventStart' => $event->start_date,
|
||||
'eventEnd' => $event->end_date,
|
||||
'rows' => $participants,
|
||||
'kitchenRequirements' => $kitchenOverview,
|
||||
'participantsForKitchenList' => $this->eventParticipants->getParticipantsWithIntolerances($event, $request),
|
||||
])->render();
|
||||
|
||||
$pdf = PdfGenerateAndDownloadProvider::fromHtml(
|
||||
$html,
|
||||
'landscape'
|
||||
);
|
||||
|
||||
return response($pdf, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="' . $listType .'.pdf"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCsvList(string $eventId, string $listType, Request $request): Response
|
||||
{
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
|
||||
$participants = $this->eventParticipants->getForList($event, $request);
|
||||
$kitchenOverview = $this->eventParticipants->getKitchenOverview($event);
|
||||
|
||||
$csv = view('csvs.' . $listType, [
|
||||
'event' => $event->name,
|
||||
'rows' => $participants,
|
||||
])->render();
|
||||
|
||||
return response($csv, 200, [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
'Content-Disposition' => 'attachment; filename="' . $listType . '.csv"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function listParticipants(string $eventId, string $listType, Request $request) : JsonResponse {
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
switch ($listType) {
|
||||
case 'by-local-group':
|
||||
$participants = $this->eventParticipants->groupByLocalGroup($event, $request);
|
||||
break;
|
||||
case 'by-participation-group':
|
||||
$participants = $this->eventParticipants->groupByParticipationType($event, $request);
|
||||
break;
|
||||
case 'signed-off':
|
||||
$participants = $this->eventParticipants->getSignedOffParticipants($event, $request);
|
||||
break;
|
||||
default:
|
||||
$participants = ['Alle Teilnehmenden' => $this->eventParticipants->getForList($event, $request)];
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'participants' => $participants,
|
||||
'listType' => $listType,
|
||||
'event' => $event->toResource()->toArray($request)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers\MailCompose;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ByGroupController extends CommonController
|
||||
{
|
||||
public function __invoke(string $eventIdentifier, string $groupType, Request $request) {
|
||||
$event = $this->events->getByIdentifier($eventIdentifier, true);
|
||||
$recipients = [];
|
||||
switch ($groupType) {
|
||||
case 'by-local-group':
|
||||
$participants = $this->eventParticipants->groupByLocalGroup($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
case 'by-participation-group':
|
||||
$participants = $this->eventParticipants->groupByParticipationType($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
case 'signed-off':
|
||||
$participants = $this->eventParticipants->getSignedOffParticipants($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
default:
|
||||
$participants = $this->eventParticipants->getForList($event, $request);
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants);
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['recipients' => $recipients]);
|
||||
}
|
||||
}
|
||||
66
app/Domains/Event/Controllers/MailCompose/SendController.php
Normal file
66
app/Domains/Event/Controllers/MailCompose/SendController.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers\MailCompose;
|
||||
|
||||
use App\Mail\ManualMails\ManualMailsCommonMail;
|
||||
use App\Mail\ManualMails\ManualMailsReportMail;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendController extends CommonController
|
||||
{
|
||||
public function __invoke(string $eventIdentifier, Request $request) {
|
||||
$recipients = $request->input('recipients')
|
||||
|> function (string $value) : string { return str_replace(';', ',', $value); }
|
||||
|> function (string $value) : array { return explode( ',', $value); };
|
||||
|
||||
$event = $this->events->getByIdentifier($eventIdentifier, true)->toResource()->toArray($request);
|
||||
$sentRecipients = [];
|
||||
$allOkay = true;
|
||||
$subject = $request->input('subject') ?? 'Neue Nachricht zu Veranstaltung "' . $event['name'] . '"';
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
if (in_array($recipient, $sentRecipients)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sentRecipients[] = $recipient;
|
||||
try {
|
||||
Mail::to(trim($recipient))->send(new ManualMailsCommonMail(
|
||||
mailSubject: $subject,
|
||||
message: $request->input('message'),
|
||||
event: $event,
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
$allOkay = false;
|
||||
}
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$reportSubject = sprintf('Sendebericht für Nachricht mit Betreff "%s"', $subject);
|
||||
|
||||
Mail::to($user->email)->send(new ManualMailsReportMail(
|
||||
mailSubject: $reportSubject,
|
||||
message: $request->input('message'),
|
||||
event: $event,
|
||||
originalRecipients: $sentRecipients
|
||||
));
|
||||
|
||||
if ($allOkay) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => sprintf(
|
||||
'E-Mail wurde erfolgreich an %1$s Personen versendet. Du hast eine Kopie an deine Mail-Adresse erhalten.',
|
||||
count($sentRecipients)
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Es gab einen Fehler beim Versenden der Nachrichten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/Domains/Event/Controllers/ParticipantController.php
Normal file
34
app/Domains/Event/Controllers/ParticipantController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\ManualCertificateOfConductionCheck\ManualCertificateOfConductionCheckCommand;
|
||||
use App\Domains\Event\Actions\ManualCertificateOfConductionCheck\ManualCertificateOfConductionCheckRequest;
|
||||
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentCommand;
|
||||
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentRequest;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
|
||||
class ParticipantController extends CommonController {
|
||||
public function markCocExisting(string $participantIdentifier, Request $request) {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
|
||||
|
||||
$cocRequest = new ManualCertificateOfConductionCheckRequest($participant);
|
||||
$cocCommand = new ManualCertificateOfConductionCheckCommand($cocRequest);
|
||||
$cocResponse = $cocCommand->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $cocResponse->success ? 'success' : 'error',
|
||||
'message' => $cocResponse->success ? 'Das eFZ wurde als gültig hinterlegt' : 'Beim Aktualisieren des eFZ-Status ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
|
||||
public function __invoke(string $participantIdentifier, Request $request) {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events)->toResource()->toArray($request);
|
||||
|
||||
return response()->json([
|
||||
'participant' => $participant,
|
||||
]);
|
||||
}
|
||||
}
|
||||
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 . '"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentCommand;
|
||||
use App\Domains\Event\Actions\ParticipantPayment\ParticipantPaymentRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParticipantPaymentController extends CommonController
|
||||
{
|
||||
public function paymentComplete(string $participantIdentifier, Request $request) {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
|
||||
|
||||
$paymentRequest = new ParticipantPaymentRequest($participant, $participant->amount);
|
||||
|
||||
$paymentCommand = new ParticipantPaymentCommand($paymentRequest);
|
||||
$paymentResponse = $paymentCommand->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $paymentResponse->success ? 'success' : 'error',
|
||||
'message' => $paymentResponse->success ? 'Die Zahlung wurde erfolgreich gebucht.' : 'Beim Buchen der Zahlung ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
|
||||
public function partialPaymentComplete(string $participantIdentifier, Request $request) {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
|
||||
|
||||
$paymentRequest = new ParticipantPaymentRequest($participant, Amount::fromString($request->input('amount')));
|
||||
|
||||
$paymentCommand = new ParticipantPaymentCommand($paymentRequest);
|
||||
$paymentResponse = $paymentCommand->execute();
|
||||
|
||||
|
||||
$amountLeft = clone($paymentResponse->amountExpected);
|
||||
$amountLeft->subtractAmount($paymentResponse->amountPaid);
|
||||
|
||||
return response()->json([
|
||||
'status' => $paymentResponse->success ? 'success' : 'error',
|
||||
'message' => $paymentResponse->success ? 'Die Zahlung wurde erfolgreich gebucht.' : 'Beim Buchen der Zahlung ist ein Fehler aufgetreten.',
|
||||
'identifier' => $participant->identifier,
|
||||
'amount' => [
|
||||
'paid' => $paymentResponse->amountPaid->toString(),
|
||||
'expected' => $paymentResponse->amountExpected->toString(),
|
||||
'actions' => $amountLeft->getAmount() != 0 ? 'inline' : 'none',
|
||||
'class' => $amountLeft->getAmount() != 0 ? 'not-paid' : 'paid',
|
||||
]
|
||||
]);
|
||||
|
||||
dd($participant);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateCommand;
|
||||
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateReSignonRequest;
|
||||
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateSignoffRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use DateTime;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParticipantReSignOnController extends CommonController
|
||||
{
|
||||
public function __invoke(string $participantIdentifier, Request $request) {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
|
||||
|
||||
$request = new SetParticipationStateReSignonRequest($participant);
|
||||
$command = new SetParticipationStateCommand($request);
|
||||
$command->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'identifier' => $participant->identifier,
|
||||
'message' => 'Die Wiederanmeldung wurde erfolgreich durchgeführt.'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateCommand;
|
||||
use App\Domains\Event\Actions\SetParticipationState\SetParticipationStateSignoffRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use DateTime;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParticipantSignOffController extends CommonController
|
||||
{
|
||||
public function __invoke(string $participantIdentifier, Request $request) {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
|
||||
|
||||
$signOffDate = DateTime::createFromFormat('Y-m-d', $request->input('cancel_date'));
|
||||
|
||||
$signOffRequest = new SetParticipationStateSignoffRequest($participant, $signOffDate);
|
||||
$signOffCommand = new SetParticipationStateCommand($signOffRequest);
|
||||
$signOffCommand->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'identifier' => $participant->identifier,
|
||||
'message' => 'Die Abmeldung wurde erfolgreich durchgeführt.'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\UpdateParticipant\UpdateParticipantCommand;
|
||||
use App\Domains\Event\Actions\UpdateParticipant\UpdateParticipantRequest;
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParticipantUpdateController extends CommonController {
|
||||
public function __invoke(string $participantIdentifier, Request $request): JsonResponse {
|
||||
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
|
||||
|
||||
$updateRequest = new UpdateParticipantRequest(
|
||||
participant: $participant,
|
||||
firstname: $request->input('firstname'),
|
||||
lastname: $request->input('lastname'),
|
||||
nickname: $request->input('nickname'),
|
||||
address_1: $request->input('address_1'),
|
||||
address_2: $request->input('address_2'),
|
||||
postcode: $request->input('postcode'),
|
||||
city: $request->input('city'),
|
||||
localgroup: $request->input('localgroup'),
|
||||
birthday: $request->input('birthday'),
|
||||
email_1: $request->input('email_1'),
|
||||
phone_1: $request->input('phone_1'),
|
||||
contact_person: $request->input('contact_person'),
|
||||
email_2: $request->input('email_2'),
|
||||
phone_2: $request->input('phone_2'),
|
||||
arrival: $request->input('arrival'),
|
||||
departure: $request->input('departure'),
|
||||
participationType: $request->input('participationType'),
|
||||
eatingHabit: $request->input('eatingHabit'),
|
||||
allergies: $request->input('allergies'),
|
||||
intolerances: $request->input('intolerances'),
|
||||
medications: $request->input('medications'),
|
||||
extendedFirstAid: $request->input('extendedFirstAid'),
|
||||
swimmingPermission: $request->input('swimmingPermission'),
|
||||
tetanusVaccination: $request->input('tetanusVaccination'),
|
||||
notes: $request->input('notes'),
|
||||
amountPaid: $request->input('amountPaid'),
|
||||
amountExpected: $request->input('amountExpected'),
|
||||
cocStatus: $request->input('cocStatus'),
|
||||
);
|
||||
|
||||
$command = new UpdateParticipantCommand($updateRequest);
|
||||
$response = $command->execute();
|
||||
|
||||
$data = [
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'identifier' => $participant->identifier,
|
||||
'participant' => $response->participant->toResource()->toArray($request),
|
||||
];
|
||||
|
||||
if ($response->cocStatus !== null) {
|
||||
$data['cocChanged'] = true;
|
||||
$data['coc']['action'] = in_array($response->cocStatus->slug, [
|
||||
EfzStatus::EFZ_STATUS_CHECKED_INVALID,
|
||||
EfzStatus::EFZ_STATUS_NOT_CHECKED]) ? 'inline' : 'none';
|
||||
$data['coc']['statusText'] = $response->cocStatus->name;
|
||||
$data['coc']['class'] = match($response->cocStatus->slug) {
|
||||
EfzStatus::EFZ_STATUS_CHECKED_INVALID => 'efz-invalid',
|
||||
EfzStatus::EFZ_STATUS_NOT_CHECKED => 'efz-not-checked',
|
||||
default => 'efz-valid',
|
||||
};
|
||||
}
|
||||
|
||||
if ($response->amountPaid !== null) {
|
||||
$amountLeft = clone($response->amountExpected);
|
||||
$amountLeft->subtractAmount($response->amountPaid);
|
||||
|
||||
$data['amountChanged'] = true;
|
||||
$data['amount'] = [
|
||||
'paid' => $response->amountPaid->toString(),
|
||||
'expected' => $response->amountExpected->toString(),
|
||||
'actions' => $amountLeft->getAmount() != 0 ? 'inline' : 'none',
|
||||
'class' => $amountLeft->getAmount() != 0 ? 'not-paid' : 'paid',
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
33
app/Domains/Event/Controllers/PaymentReminderController.php
Normal file
33
app/Domains/Event/Controllers/PaymentReminderController.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\SendMissingPaymentMails\SendMissingPaymentMailsCommand;
|
||||
use App\Domains\Event\Actions\SendMissingPaymentMails\SendMissingPaymentMailsRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PaymentReminderController extends CommonController
|
||||
{
|
||||
public function __invoke(string $eventIdentifier, Request $request)
|
||||
{
|
||||
$event = $this->events->getByIdentifier($eventIdentifier, true);
|
||||
|
||||
$sendPaymentReminderMailsRequest = new SendMissingPaymentMailsRequest(
|
||||
event: $event,
|
||||
eventParticipants: $this->eventParticipants,
|
||||
httpRequest: $request
|
||||
);
|
||||
|
||||
$sendPaymentReminderMailsCommand = new SendMissingPaymentMailsCommand(request: $sendPaymentReminderMailsRequest);
|
||||
$sendPaymentReminderResponse = $sendPaymentReminderMailsCommand->execute();
|
||||
|
||||
return response()->json([
|
||||
'success' => $sendPaymentReminderResponse->success,
|
||||
'message' => $sendPaymentReminderResponse->success ?
|
||||
sprintf('Es wurden %1$s Personen über fehlende Teilnahmebeiträge informiert', $sendPaymentReminderResponse->remindedParticipants) :
|
||||
'Beim Senden der Benachrichtigungen ist ein Fehler aufgetreten.',
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
||||
182
app/Domains/Event/Controllers/SignupController.php
Normal file
182
app/Domains/Event/Controllers/SignupController.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckCommand;
|
||||
use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckRequest;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpCommand;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpRequest;
|
||||
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
||||
use App\Models\Tenant;
|
||||
use App\Providers\DoubleCheckEventRegistrationProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\UserResource;
|
||||
use App\Scopes\CommonController;
|
||||
use DateTime;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SignupController extends CommonController {
|
||||
public function __invoke(string $eventId, Request $request) {
|
||||
$availableEvents = [];
|
||||
foreach ($this->events->getAvailable(false) as $event) {
|
||||
$availableEvents[] = $event->toResource()->toArray($request);
|
||||
};
|
||||
|
||||
$event = $this->events->getByIdentifier($eventId, false)?->toResource()->toArray($request);
|
||||
|
||||
$participantData = [
|
||||
'firstname' => '',
|
||||
'lastname' => '',
|
||||
];
|
||||
|
||||
if (auth()->check()) {
|
||||
$user = new UserResource(auth()->user())->toArray($request);
|
||||
|
||||
$participantData = [
|
||||
'id' => $user['id'],
|
||||
'firstname' => $user['firstname'],
|
||||
'lastname' => $user['lastname'],
|
||||
'nickname' => $user['nickname'],
|
||||
'email' => $user['email'],
|
||||
'phone' => $user['phone'],
|
||||
'postcode' => $user['postcode'],
|
||||
'city' => $user['city'],
|
||||
'address_1' => $user['address_1'],
|
||||
'address_2' => $user['address_2'],
|
||||
'birthday' => $user['birthday'],
|
||||
'localGroup' => $user['localGroup'],
|
||||
'allergies' => $user['allergies'],
|
||||
'intolerances' => $user['intolerances'],
|
||||
'eating_habit' => $user['eating_habits'],
|
||||
'medications' => $user['medications'],
|
||||
'tetanusVaccination' => $user['tetanus_vaccination'],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Event/Signup', [
|
||||
'event' => $event,
|
||||
'availableEvents' => $availableEvents,
|
||||
'localGroups' => $event['contributingLocalGroups'],
|
||||
'participantData' => $participantData,
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function signUp(int $eventId, Request $request) {
|
||||
$event = $this->events->getById($eventId, false);
|
||||
$eventResource = $event->toResource();
|
||||
$registrationData = $request->input('registration_data');
|
||||
$siblingReduction = $registrationData['sibling'] === 'true';
|
||||
|
||||
|
||||
$arrival = \DateTime::createFromFormat('Y-m-d', $registrationData['arrival']);
|
||||
$departure = \DateTime::createFromFormat('Y-m-d', $registrationData['departure']);
|
||||
$tetanusVaccination = $registrationData['tetanusVaccination'] ? \DateTime::createFromFormat('Y-m-d', $registrationData['tetanusVaccination']) : null;
|
||||
|
||||
$doubleCheckEventRegistrationProvider = new DoubleCheckEventRegistrationProvider(
|
||||
$event,
|
||||
$registrationData['vorname'],
|
||||
$registrationData['nachname'],
|
||||
$registrationData['email_1'],
|
||||
DateTime::createFromFormat('Y-m-d', $registrationData['geburtsdatum']));
|
||||
|
||||
if ($doubleCheckEventRegistrationProvider->isRegistered()) {
|
||||
return response()->json(['status' => 'exists']);
|
||||
}
|
||||
|
||||
$amount = $eventResource->calculateAmount(
|
||||
$registrationData['participationType'],
|
||||
$registrationData['beitrag'],
|
||||
$arrival,
|
||||
$departure,
|
||||
$siblingReduction
|
||||
);
|
||||
|
||||
$signupRequest = new SignUpRequest(
|
||||
$event,$registrationData['userId'],
|
||||
$registrationData['vorname'],
|
||||
$registrationData['nachname'],
|
||||
$registrationData['pfadiname'],
|
||||
$registrationData['participationType'],
|
||||
Tenant::findOrFail($registrationData['localGroup']),
|
||||
\DateTime::createFromFormat('Y-m-d', $registrationData['geburtsdatum']),
|
||||
$registrationData['address1'],
|
||||
$registrationData['address2'],
|
||||
$registrationData['plz'],
|
||||
$registrationData['ort'],
|
||||
$registrationData['email_1'],
|
||||
$registrationData['telefon_1'],
|
||||
$registrationData['email_2'],
|
||||
$registrationData['telefon_2'],
|
||||
$registrationData['ansprechpartner'],
|
||||
$registrationData['allergien'],
|
||||
$registrationData['intolerances'],
|
||||
$registrationData['medikamente'],
|
||||
$tetanusVaccination,
|
||||
$registrationData['essgewohnheit'],
|
||||
$registrationData['badeerlaubnis'],
|
||||
$registrationData['first_aid'],
|
||||
$registrationData['foto']['socialmedia'],
|
||||
$registrationData['foto']['print'],
|
||||
$registrationData['foto']['webseite'],
|
||||
$registrationData['foto']['partner'],
|
||||
$registrationData['foto']['intern'],
|
||||
$arrival,
|
||||
$departure,
|
||||
$registrationData['anreise_essen'],
|
||||
$registrationData['abreise_essen'],
|
||||
$registrationData['anmerkungen'],
|
||||
$amount
|
||||
);
|
||||
|
||||
$signupCommand = new SignUpCommand($signupRequest);
|
||||
$signupResponse = $signupCommand->execute();
|
||||
|
||||
// 4. Addons registrieren
|
||||
|
||||
|
||||
$certificateOfConductionCheckRequest = new CertificateOfConductionCheckRequest($signupResponse->participant);
|
||||
$certificateOfConductionCheckCommand = new CertificateOfConductionCheckCommand($certificateOfConductionCheckRequest);
|
||||
$certificateOfConductionCheckResponse = $certificateOfConductionCheckCommand->execute();
|
||||
|
||||
$signupResponse->participant->efz_status = $certificateOfConductionCheckResponse->status;
|
||||
$signupResponse->participant->save();
|
||||
|
||||
Mail::to($signupResponse->participant->email_1)->send(new EventSignUpSuccessfullMail(
|
||||
participant: $signupResponse->participant,
|
||||
));
|
||||
|
||||
if ($signupResponse->participant->email_2 !== null) {
|
||||
Mail::to($signupResponse->participant->email_2)->send(new EventSignUpSuccessfullMail(
|
||||
participant: $signupResponse->participant,
|
||||
));
|
||||
}
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'participant' => $signupResponse->participant->toResource()->toArray($request),
|
||||
'status' => 'success',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function calculateAmount(int $eventId, Request $request, bool $forDisplay = true) : JsonResponse | float {
|
||||
$event = $this->events->getById($eventId, false)->toResource();
|
||||
|
||||
$siblingReduction = $request->input('sibling') === 'true';
|
||||
|
||||
return response()->json(['amount' =>
|
||||
$event->calculateAmount(
|
||||
$request->input('participationType'),
|
||||
$request->input('beitrag'),
|
||||
\DateTime::createFromFormat('Y-m-d', $request->input('arrival')),
|
||||
\DateTime::createFromFormat('Y-m-d', $request->input('departure')),
|
||||
$siblingReduction
|
||||
)->toString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
64
app/Domains/Event/Routes/api.php
Normal file
64
app/Domains/Event/Routes/api.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Event\Controllers\CreateController;
|
||||
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;
|
||||
use App\Domains\Event\Controllers\ParticipantUpdateController;
|
||||
use App\Domains\Event\Controllers\PaymentReminderController;
|
||||
use App\Domains\Event\Controllers\SignupController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('api/v1')
|
||||
->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('event')->group(function () {
|
||||
Route::post('{eventId}/calculate-amount', [SignupController::class, 'calculateAmount']);
|
||||
Route::post('{eventId}/signup', [SignupController::class, 'signUp']);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
Route::get('{eventIdentifier}/send-payment-reminder', PaymentReminderController::class);
|
||||
|
||||
Route::prefix('/details/{eventId}') ->group(function () {
|
||||
Route::get('/summary', [DetailsController::class, 'summary']);
|
||||
|
||||
Route::get('/participants/{listType}', [DetailsController::class, 'listParticipants']);
|
||||
|
||||
|
||||
Route::post('/event-managers', [DetailsController::class, 'updateEventManagers']);
|
||||
Route::post('/participation-fees', [DetailsController::class, 'updateParticipationFees']);
|
||||
Route::post('/common-settings', [DetailsController::class, 'updateCommonSettings']);
|
||||
});
|
||||
|
||||
|
||||
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']);
|
||||
Route::post('/signoff', ParticipantSignOffController::class);
|
||||
Route::post('/re-signon', ParticipantReSignOnController::class);
|
||||
Route::post('/update', ParticipantUpdateController::class);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
25
app/Domains/Event/Routes/web.php
Normal file
25
app/Domains/Event/Routes/web.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Event\Controllers\AvailableEventsController;
|
||||
use App\Domains\Event\Controllers\CreateController;
|
||||
use App\Domains\Event\Controllers\DetailsController;
|
||||
use App\Domains\Event\Controllers\SignupController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('event')->group(function () {
|
||||
Route::get('/available-events', AvailableEventsController::class);
|
||||
|
||||
|
||||
Route::get('/{eventId}', SignupController::class);
|
||||
Route::get('/{eventId}/signup', SignupController::class);
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/new', CreateController::class);
|
||||
Route::get('/details/{eventId}', DetailsController::class);
|
||||
Route::get('/details/{eventId}/pdf/{listType}', [DetailsController::class, 'downloadPdfList']);
|
||||
Route::get('/details/{eventId}/csv/{listType}', [DetailsController::class, 'downloadCsvList']);
|
||||
});
|
||||
});
|
||||
});
|
||||
293
app/Domains/Event/Views/Create.vue
Normal file
293
app/Domains/Event/Views/Create.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import {reactive, watch, ref, computed} from 'vue'
|
||||
import { subWeeks, format, parseISO, isValid, addDays } from 'date-fns'
|
||||
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ParticipationFees from "./Partials/ParticipationFees.vue";
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
'emailAddress': String,
|
||||
'eventAccount': String,
|
||||
'eventIban': String,
|
||||
"eventPayPerDay": Boolean,
|
||||
"participationFeeType": String,
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
|
||||
const formData = reactive({
|
||||
eventName: '',
|
||||
eventPostalCode: '',
|
||||
eventLocation: '',
|
||||
eventEmail: props.emailAddress ? props.emailAddress : '',
|
||||
eventBegin: '',
|
||||
eventEnd: '',
|
||||
eventEarlyBirdEnd: '',
|
||||
eventEarlyBirdEndAmountIncrease: 50,
|
||||
eventRegistrationFinalEnd: '',
|
||||
eventAccount: props.eventAccount ? props.eventAccount : '',
|
||||
eventIban: props.eventIban ? props.eventIban : '',
|
||||
eventPayDirectly: true,
|
||||
eventPayPerDay: props.eventPayPerDay ? props.eventPayPerDay : false,
|
||||
eventParticipationFeeType: props.participationFeeType ? props.participationFeeType : 'fixed',
|
||||
});
|
||||
|
||||
watch(
|
||||
() => formData.eventBegin,
|
||||
(newValue) => {
|
||||
if (!newValue) return
|
||||
|
||||
const beginDate = parseISO(newValue)
|
||||
|
||||
if (!isValid(beginDate)) return
|
||||
|
||||
const fourWeeksBefore = subWeeks(beginDate, 4)
|
||||
const twoWeeksBefore = subWeeks(beginDate, 2)
|
||||
const threeDaysAfter = addDays(beginDate, 2)
|
||||
|
||||
formData.eventEarlyBirdEnd = format(
|
||||
fourWeeksBefore,
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
|
||||
formData.eventRegistrationFinalEnd = format(
|
||||
twoWeeksBefore,
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
|
||||
formData.eventEnd = format(
|
||||
threeDaysAfter,
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const formIsValid = computed(() => {
|
||||
errors.eventEmail = '';
|
||||
errors.eventName = '';
|
||||
errors.eventLocation = '';
|
||||
errors.eventPostalCode = '';
|
||||
errors.eventBegin = '';
|
||||
errors.eventEnd = '';
|
||||
errors.eventEarlyBirdEnd = '';
|
||||
errors.eventRegistrationFinalEnd = '';
|
||||
errors.eventAccount = '';
|
||||
errors.eventIban = '';
|
||||
|
||||
var returnValue = true;
|
||||
|
||||
if (!formData.eventName) {
|
||||
errors.eventName = 'Bitte gib den Veranstaltungsnamen ein'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventEmail) {
|
||||
errors.eventEmail = 'Bitte gib die E-Mail-Adresse der Veranstaltungsleitung für Rückfragen der Teilnehmenden ein'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventLocation) {
|
||||
errors.eventLocation = 'Bitte gib den Veranstaltungsort ein'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventPostalCode) {
|
||||
errors.eventPostalCode = 'Bitte gib die Postleitzahl des Veranstaltungsorts ein'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventBegin) {
|
||||
errors.eventBegin = 'Bitte gib das Anfangsdatum der Veranstaltung ein'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventEnd ||formData.eventEnd < formData.eventBegin ) {
|
||||
errors.eventEnd = 'Das Enddatum darf nicht vor dem Anfangsdatum liegen'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventEarlyBirdEnd ||formData.eventEarlyBirdEnd > formData.eventBegin ) {
|
||||
errors.eventEarlyBirdEnd = 'Das Enddatum der Early-Bird-Phase muss vor dem Veranstaltungsbeginn liegen'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventRegistrationFinalEnd ||formData.eventRegistrationFinalEnd > formData.eventBegin ) {
|
||||
errors.eventRegistrationFinalEnd = 'Der Anmeldeschluss darf nicht nach dem Veranstaltungsbeginn liegen'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventAccount) {
|
||||
errors.eventAccount = 'Bitte gib an, auf wen das Veranstaltungskonto für eingehende Beiträge läuft'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
if (!formData.eventIban) {
|
||||
errors.eventIban = 'Bitte gib die IBAN des Kontos für Teilnahmebeiträge ein'
|
||||
returnValue = false
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
|
||||
})
|
||||
|
||||
const showParticipationFees = ref(false)
|
||||
const newEvent = ref(null)
|
||||
|
||||
async function createEvent() {
|
||||
if (!formIsValid.value) return false
|
||||
|
||||
const data = await request("/api/v1/event/create", {
|
||||
method: "POST",
|
||||
body: {
|
||||
eventName: formData.eventName,
|
||||
eventPostalCode: formData.eventPostalCode,
|
||||
eventLocation: formData.eventLocation,
|
||||
eventEmail: formData.eventEmail,
|
||||
eventBegin: formData.eventBegin,
|
||||
eventEnd: formData.eventEnd,
|
||||
eventEarlyBirdEnd: formData.eventEarlyBirdEnd,
|
||||
eventEarlyBirdEndAmountIncrease: formData.eventEarlyBirdEndAmountIncrease,
|
||||
eventRegistrationFinalEnd: formData.eventRegistrationFinalEnd,
|
||||
eventAccount: formData.eventAccount,
|
||||
eventIban: formData.eventIban,
|
||||
eventPayDirectly: formData.eventPayDirectly,
|
||||
eventPayPerDay: formData.eventPayPerDay,
|
||||
eventParticipationFeeType: formData.eventParticipationFeeType,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.status !== 'success') {
|
||||
toast.error(data.message);
|
||||
return false;
|
||||
} else {
|
||||
newEvent.value = data.event;
|
||||
showParticipationFees.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function finishCreation() {
|
||||
window.location.href = '/event/details/' + newEvent.value.identifier;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Neue Veranstaltung">
|
||||
<fieldset>
|
||||
<legend>
|
||||
<span style="font-weight: bolder;">Grundlegende Veranstaltungsdaten</span>
|
||||
</legend>
|
||||
<ParticipationFees v-if="showParticipationFees" :event="newEvent" @close="finishCreation" />
|
||||
|
||||
<table style="margin-top: 40px; width: 100%" v-else>
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Veranstaltungsname</th>
|
||||
<td class="height-50"><input type="text" v-model="formData.eventName" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventName" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Veranstaltungsort</th>
|
||||
<td class="height-50"><input type="text" v-model="formData.eventLocation" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventLocation" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Postleitzahl des Veranstaltungsorts</th>
|
||||
<td class="height-50"><input type="text" v-model="formData.eventPostalCode" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventPostalCode" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">E-Mail-Adresse der Veranstaltungsleitung</th>
|
||||
<td class="height-50"><input type="email" v-model="formData.eventEmail" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventEmail" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Beginn</th>
|
||||
<td class="height-50"><input type="date" v-model="formData.eventBegin" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventBegin" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Ende</th>
|
||||
<td class="height-50"><input type="date" v-model="formData.eventEnd" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventEnd" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Ende Early-Bird-Phase</th>
|
||||
<td class="height-50"><input type="date" v-model="formData.eventEarlyBirdEnd" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventEarlyBirdEnd" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Finaler Anmeldeschluss</th>
|
||||
<td class="height-50"><input type="date" v-model="formData.eventRegistrationFinalEnd" class="width-half-full" />
|
||||
<ErrorText :message="errors.eventRegistrationFinalEnd" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Beitragsart</th>
|
||||
<td class="height-50">
|
||||
<select v-model="formData.eventParticipationFeeType" class="width-half-full">
|
||||
<option value="fixed">Festpreis</option>
|
||||
<option value="solidarity">Solidaritätsprinzip</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Preiserhöhung nach Early-Bird-Phase</th>
|
||||
<td class="height-50"><input type="number" v-model="formData.eventEarlyBirdEndAmountIncrease" class="width-tiny" />%</td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Veranstsaltungs-Konto</th>
|
||||
<td class="height-50"><input type="text" v-model="formData.eventAccount" class="width-full" />
|
||||
<ErrorText :message="errors.eventAccount" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Veranstaltungs-IBAN</th>
|
||||
<td class="height-50"><input type="text" v-model="formData.eventIban" class="width-full" />
|
||||
<ErrorText :message="errors.eventIban" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<td colspan="2" style="font-weight: bold;">
|
||||
<input type="checkbox" v-model="formData.eventPayDirectly">
|
||||
Teilnehmende zahlen direkt aufs Veranstaltungskonto
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<td colspan="2" style="font-weight: bold;">
|
||||
<input type="checkbox" v-model="formData.eventPayPerDay">
|
||||
Beitrag abhängig von Anwesenheitstagen
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" class="pt-20">
|
||||
<input type="button" value="Veranstaltung erstellen" @click="createEvent" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</fieldset>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
60
app/Domains/Event/Views/Details.vue
Normal file
60
app/Domains/Event/Views/Details.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
import {reactive, inject, onMounted} from 'vue';
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import ParticipantsList from "./Partials/ParticipantsList.vue";
|
||||
import Overview from "./Partials/Overview.vue";
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
})
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Übersicht',
|
||||
component: Overview,
|
||||
endpoint: "/api/v1/event/details/" + props.event.id + '/summary',
|
||||
},
|
||||
{
|
||||
title: 'Alle Teilnehmendenden',
|
||||
component: ParticipantsList,
|
||||
endpoint: "/api/v1/event/details/" + props.event.identifier + '/participants/all',
|
||||
},
|
||||
{
|
||||
title: 'Teilis nach Stamm',
|
||||
component: ParticipantsList,
|
||||
endpoint: "/api/v1/event/details/" + props.event.identifier + '/participants/by-local-group',
|
||||
},
|
||||
{
|
||||
title: 'Teilis nach Teili-Gruppe',
|
||||
component: ParticipantsList,
|
||||
endpoint: "/api/v1/event/details/" + props.event.identifier + '/participants/by-participation-group',
|
||||
},
|
||||
{
|
||||
title: 'Abgemeldete Teilis',
|
||||
component: ParticipantsList,
|
||||
endpoint: "/api/v1/event/details/" + props.event.identifier + '/participants/signed-off',
|
||||
},
|
||||
{
|
||||
title: 'Zusätze',
|
||||
component: ParticipantsList,
|
||||
endpoint: "/api/v1/cost-unit/open/archived-cost-units",
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout :title="'Veranstaltungsdetails ' + props.event.name">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
19
app/Domains/Event/Views/ListAvailable.vue
Normal file
19
app/Domains/Event/Views/ListAvailable.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import AvailableEvents from "./Partials/AvailableEvents.vue";
|
||||
|
||||
const props = defineProps({
|
||||
events: Array,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Verfügbare Veranstaltungen">
|
||||
<AvailableEvents :events="props.events" />
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
100
app/Domains/Event/Views/Partials/AvailableEvents.vue
Normal file
100
app/Domains/Event/Views/Partials/AvailableEvents.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
|
||||
import ShadowedBox from "../../../../Views/Components/ShadowedBox.vue";
|
||||
|
||||
const props = defineProps({
|
||||
events: Array,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 95%; margin: 20px auto;">
|
||||
|
||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
||||
Aktuell sind keine Veranstaltungen verfügbar.
|
||||
</div>
|
||||
|
||||
<shadowed-box
|
||||
v-for="event in props.events"
|
||||
:key="event.id"
|
||||
style="padding: 24px; margin-bottom: 20px;"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px;">
|
||||
<div>
|
||||
<h2 style="margin: 0 0 4px 0; font-size: 1.25rem;">{{ event.name }}</h2>
|
||||
<span style="color: #6b7280; font-size: 0.9rem;">{{ event.postalCode }} {{ event.location }}</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="event.registrationAllowed"
|
||||
style="background: #d1fae5; color: #065f46; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
||||
>
|
||||
Anmeldung offen
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
style="background: #fee2e2; color: #991b1b; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
||||
>
|
||||
Anmeldung geschlossen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr style="margin: 16px 0; border: none; border-top: 1px solid #e5e7eb;" />
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Zeitraum</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.eventBegin }} – {{ event.eventEnd }} ({{ event.duration }} Tage)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Veranstaltungsort</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.postalCode }} {{ event.location }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Frühbuchen bis</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.earlyBirdEnd.formatted }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Anmeldeschluss</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.registrationFinalEnd.formatted }}</td>
|
||||
</tr>
|
||||
<tr v-if="event.email">
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Kontakt</th>
|
||||
<td style="padding: 6px 0;">
|
||||
<a :href="'mailto:' + event.email" style="color: #2563eb;">{{ event.email }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
||||
<a
|
||||
:href="'/event/' + event.identifier + '/signup'"
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
opacity: 1;
|
||||
transition: background-color 0.2s;
|
||||
"
|
||||
:style="{ opacity: event.registrationAllowed ? '1' : '0.5', pointerEvents: event.registrationAllowed ? 'auto' : 'none' }"
|
||||
>
|
||||
Zur Anmeldung →
|
||||
</a>
|
||||
</div>
|
||||
</shadowed-box>
|
||||
|
||||
</div><div style="width: 95%; margin: 20px auto;">
|
||||
|
||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
||||
Aktuell sind keine Veranstaltungen verfügbar.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
258
app/Domains/Event/Views/Partials/CommonSettings.vue
Normal file
258
app/Domains/Event/Views/Partials/CommonSettings.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
})
|
||||
|
||||
|
||||
const dynmicProps = reactive({
|
||||
localGroups: [],
|
||||
eatingHabits:[]
|
||||
});
|
||||
|
||||
const contributingLocalGroups = ref([])
|
||||
const eatingHabits = ref([]);
|
||||
|
||||
const errors = reactive({})
|
||||
|
||||
const formData = reactive({
|
||||
contributingLocalGroups: contributingLocalGroups.value,
|
||||
eventName: props.event.name,
|
||||
eventLocation: props.event.location,
|
||||
postalCode: props.event.postalCode,
|
||||
email: props.event.email,
|
||||
earlyBirdEnd: props.event.earlyBirdEnd.internal,
|
||||
registrationFinalEnd: props.event.registrationFinalEnd.internal,
|
||||
alcoholicsAge: props.event.alcoholicsAge,
|
||||
eatingHabits: eatingHabits.value,
|
||||
sendWeeklyReports: props.event.sendWeeklyReports,
|
||||
registrationAllowed: props.event.registrationAllowed,
|
||||
flatSupport: props.event.flatSupportEdit,
|
||||
supportPerson: props.event.supportPersonIndex,
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-event-setting-data');
|
||||
const data = await response.json();
|
||||
Object.assign(dynmicProps, data);
|
||||
|
||||
contributingLocalGroups.value = props.event.contributingLocalGroups?.map(t => t.id) ?? []
|
||||
eatingHabits.value = props.event.eatingHabits?.map(t => t.id) ?? []
|
||||
});
|
||||
|
||||
async function save() {
|
||||
const response = await request('/api/v1/event/details/' + props.event.id + '/common-settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: {
|
||||
eventName: formData.eventName,
|
||||
eventLocation: formData.eventLocation,
|
||||
postalCode: formData.postalCode,
|
||||
email: formData.email,
|
||||
earlyBirdEnd: formData.earlyBirdEnd,
|
||||
registrationFinalEnd: formData.registrationFinalEnd,
|
||||
alcoholicsAge: formData.alcoholicsAge,
|
||||
sendWeeklyReports: formData.sendWeeklyReports,
|
||||
registrationAllowed: formData.registrationAllowed,
|
||||
flatSupport: formData.flatSupport,
|
||||
supportPerson: formData.supportPerson,
|
||||
contributingLocalGroups: contributingLocalGroups.value,
|
||||
eatingHabits: eatingHabits.value,
|
||||
}
|
||||
})
|
||||
|
||||
if (response.status === 'success') {
|
||||
toast.success('Einstellungen wurden erfolgreich gespeichert.')
|
||||
emit('close')
|
||||
} else {
|
||||
toast.error('Beim Speichern ist ein Fehler aufgetreten.')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Einstellungen</h2>
|
||||
<div class="container">
|
||||
<div class="row top">
|
||||
<div class="left">
|
||||
<table class="event-settings-table" style="width: 80%;">
|
||||
<tr>
|
||||
<th>Veranstaltungsname</th>
|
||||
<td>
|
||||
<input type="text" v-model="formData.eventName" class="width-full" /><br />
|
||||
<ErrorText :message="errors.eventName" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Veranstaltungsort</th>
|
||||
<td>
|
||||
<input type="text" v-model="formData.eventLocation" class="width-full" /><br />
|
||||
<ErrorText :message="errors.eventLocation" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Postleitzahl des Veranstaltungsorts</th>
|
||||
<td>
|
||||
<input type="text" v-model="formData.postalCode" class="width-full" /><br />
|
||||
<ErrorText :message="errors.eventPostalCode" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>E-Mail-Adresse der Veranstaltungsleitung</th>
|
||||
<td>
|
||||
<input type="text" v-model="formData.email" class="width-full" /><br />
|
||||
<ErrorText :message="errors.eventEmail" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Ende der EarlyBird-Phase</th>
|
||||
<td>
|
||||
<input type="date" v-model="formData.earlyBirdEnd" class="width-full" /><br />
|
||||
<ErrorText :message="errors.earlyBirdEnd" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Finaler Anmeldeschluss</th>
|
||||
<td>
|
||||
<input type="date" v-model="formData.registrationFinalEnd" class="width-full" /><br />
|
||||
<ErrorText :message="errors.registrationFinalEnd" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Fördermittel</th>
|
||||
<td>
|
||||
<amountInput v-model="formData.supportPerson" clasS="width-small" /> Euro p.P. / Tag
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Zuschüsse</th>
|
||||
<td>
|
||||
<amountInput v-model="formData.flatSupport" clasS="width-small" /> Euro pauschal
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Mindestalter für Alkoholkonsum</th>
|
||||
<td>
|
||||
<input type="number" v-model="formData.alcoholicsAge" class="width-tiny" /><br />
|
||||
<ErrorText :message="errors.alcoholicsAge" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 25px !important;">
|
||||
<input type="checkbox" v-model="formData.sendWeeklyReports" id="sendWeeklyReports" />
|
||||
<label for="sendWeeklyReports">Wöchentliche Zusammenfassung per E-Mail an Stämme schicken</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="checkbox" v-model="formData.registrationAllowed" id="registrationAllowed" />
|
||||
<label for="registrationAllowed">Veranstaltung ist für Anmeldungen geöffnet</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="right">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Teilnehmende Stämme</th>
|
||||
</tr>
|
||||
<tr v-for="localGroup in dynmicProps.localGroups">
|
||||
<td>
|
||||
<input type="checkbox" :id="'localgroup_' + localGroup.id" :value="localGroup.id" v-model="contributingLocalGroups" />
|
||||
<label style="padding-left: 5px;" :for="'localgroup_' + localGroup.id">{{localGroup.name}}</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th style="padding-top: 40px !important;">Angebotene Ernährung</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="eatingHabit in dynmicProps.eatingHabits">
|
||||
<td>
|
||||
<input type="checkbox" :id="'eatinghabit' + eatingHabit.id" :value="eatingHabit.id" v-model="eatingHabits" />
|
||||
<label style="padding-left: 5px;" :for="'eatinghabit' + eatingHabit.id">{{eatingHabit.name}}</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row bott">
|
||||
<input type="button" value="Speichern" @click="save" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* Abstand zwischen den Zeilen */
|
||||
width: 95%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 10px; /* Abstand zwischen den Spalten */
|
||||
}
|
||||
|
||||
.row.top .left {
|
||||
flex: 0 0 70%; /* feste Breite von 80% */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row.top .right {
|
||||
flex: 0 0 30%; /* feste Breite von 20% */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row.bottom {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.event-settings-table {
|
||||
|
||||
}
|
||||
|
||||
.event-settings-table tr {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.event-settings-table td {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.event-settings-table th {
|
||||
vertical-align: top;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
</style>
|
||||
63
app/Domains/Event/Views/Partials/EventManagement.vue
Normal file
63
app/Domains/Event/Views/Partials/EventManagement.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||
const selectedManagers = ref([])
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const props = defineProps({
|
||||
event: Object
|
||||
})
|
||||
|
||||
|
||||
const commonProps = reactive({
|
||||
activeUsers: [],
|
||||
});
|
||||
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||
const data = await response.json();
|
||||
Object.assign(commonProps, data);
|
||||
|
||||
selectedManagers.value = props.event.managers?.map(t => t.id) ?? []
|
||||
});
|
||||
|
||||
|
||||
async function updateManagers() {
|
||||
|
||||
const response = await request('/api/v1/event/details/' + props.event.id + '/event-managers', {
|
||||
method: "POST",
|
||||
body: {
|
||||
selectedManagers: selectedManagers.value,
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 'success') {
|
||||
toast.success('Einstellungen wurden erfolgreich gespeichert.')
|
||||
emit('close')
|
||||
} else {
|
||||
toast.error('Beim Speichern ist ein Fehler aufgetreten.')
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3>Aktionsleitung:</h3>
|
||||
<p v-for="user in commonProps.activeUsers">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="'user_' + user.id"
|
||||
:value="user.id"
|
||||
v-model="selectedManagers"
|
||||
/>
|
||||
<label :for="'user_' + user.id">{{user.fullname}}</label>
|
||||
</p>
|
||||
|
||||
<input type="button" value="Speichern" @click="updateManagers" />
|
||||
</template>
|
||||
|
||||
114
app/Domains/Event/Views/Partials/MailCompose.vue
Normal file
114
app/Domains/Event/Views/Partials/MailCompose.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import InfoText from "../../../../Views/Components/InfoText.vue";
|
||||
const { request } = useAjax();
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
mailToType: String,
|
||||
recipientIdentifier: String,
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
recipients: null,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const groupTypes = ['all', 'signed-off', 'by-local-group', 'by-participation-group'];
|
||||
|
||||
if (!groupTypes.includes(props.mailToType)) {
|
||||
console.error('Unknown recipient identifier:', props.recipientIdentifier);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await request('/api/v1/event/' + props.event.identifier + '/mailing/compose/to-group/' + props.mailToType, {
|
||||
method: "POST",
|
||||
body: {
|
||||
groupName: props.recipientIdentifier
|
||||
}
|
||||
});
|
||||
|
||||
data.recipients = response.recipients;
|
||||
form.recipients = data.recipients.join(', ');
|
||||
});
|
||||
|
||||
const errorMessage = ref(null)
|
||||
const infoMessage = ref(null)
|
||||
|
||||
const emit = defineEmits([
|
||||
'closeComposer',
|
||||
|
||||
]);
|
||||
|
||||
function close() {
|
||||
emit('closeComposer');
|
||||
}
|
||||
|
||||
|
||||
async function sendMail() {
|
||||
document.getElementById('sendMessageButton').style.display = 'none';
|
||||
infoMessage.value = 'Die Rundmail wird nun gesendet. Dies kann einen Moment dauern. Bitte verlasse diese Seite nicht.'
|
||||
|
||||
toast.info('Die Rundmail wird nun gesendet. Dies kann einen Moment dauern. Bitte verlasse diese Seite nicht.')
|
||||
const response = await request('/api/v1/event/' + props.event.identifier + '/mailing/send', {
|
||||
method: "POST",
|
||||
body: {
|
||||
recipients: form.recipients,
|
||||
subject: form.subject,
|
||||
message: form.message,
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
infoMessage.value = null
|
||||
document.getElementById('sendMessageButton').style.display = 'block';
|
||||
close();
|
||||
toast.success(response.message)
|
||||
|
||||
} else {
|
||||
infoMessage.value = null
|
||||
document.getElementById('sendMessageButton').style.display = 'block';
|
||||
errorMessage.value = response.message
|
||||
toast.error(response.message)
|
||||
}
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
recipients: '',
|
||||
sendCopy: true,
|
||||
subject: '',
|
||||
message: '',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>E-Mail senden</h2>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div>
|
||||
<label style="font-weight: bold">Empfänger*innen</label>
|
||||
<textarea v-model="form.recipients" placeholder="Senden an" style="width: 100%;" rows="3"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-weight: bold">Betreff</label>
|
||||
<input type="text" v-model="form.subject" placeholder="Betreff" style="width: 100%;" />
|
||||
</div><br /><br />
|
||||
<div>
|
||||
<label style="font-weight: bold">Nachricht</label>
|
||||
<TextEditor v-model="form.message" />
|
||||
</div>
|
||||
<strong><ErrorText :message="errorMessage" /></strong>
|
||||
|
||||
</div>
|
||||
<info-text :message="infoMessage" />
|
||||
<input type="button" id="sendMessageButton" @click="sendMail" value="Senden" class="" />
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
233
app/Domains/Event/Views/Partials/Overview.vue
Normal file
233
app/Domains/Event/Views/Partials/Overview.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import ParticipationFees from "./ParticipationFees.vue";
|
||||
import ParticipationSummary from "./ParticipationSummary.vue";
|
||||
import CommonSettings from "./CommonSettings.vue";
|
||||
import EventManagement from "./EventManagement.vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import MailCompose from "./MailCompose.vue";
|
||||
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
})
|
||||
|
||||
const dynamicProps = reactive({
|
||||
event : null,
|
||||
});
|
||||
|
||||
const displayData = ref('main');
|
||||
const showEventData = ref(false);
|
||||
|
||||
|
||||
async function showMain() {
|
||||
const response = await fetch("/api/v1/event/details/" + props.data.event.id + '/summary');
|
||||
const data = await response.json();
|
||||
Object.assign(dynamicProps, data);
|
||||
displayData.value = 'main';
|
||||
}
|
||||
|
||||
async function showCommonSettings() {
|
||||
displayData.value = 'commonSettings';
|
||||
}
|
||||
|
||||
async function showParticipationFees() {
|
||||
displayData.value = 'participationFees';
|
||||
}
|
||||
|
||||
async function showEventManagement() {
|
||||
displayData.value = 'eventManagement';
|
||||
}
|
||||
|
||||
async function eventData() {
|
||||
showEventData.value = true;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch("/api/v1/event/details/" + props.data.event.id + '/summary');
|
||||
const data = await response.json();
|
||||
Object.assign(dynamicProps, data);
|
||||
});
|
||||
|
||||
const mailCompose = ref(false);
|
||||
|
||||
function mailToGroup() {
|
||||
mailCompose.value = true
|
||||
}
|
||||
|
||||
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/" );
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
toast.success(data.message)
|
||||
} else {
|
||||
toast.error(data.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ParticipationFees v-if="displayData === 'participationFees'" :event="dynamicProps.event" @close="showMain" />
|
||||
<CommonSettings v-else-if="displayData === 'commonSettings'" :event="dynamicProps.event" @close="showMain" />
|
||||
<EventManagement v-else-if="displayData === 'eventManagement'" :event="dynamicProps.event" @close="showMain" />
|
||||
|
||||
|
||||
|
||||
<div class="event-flexbox" v-else>
|
||||
<div class="event-flexbox-row top">
|
||||
<div class="left"><ParticipationSummary v-if="dynamicProps.event" :event="dynamicProps.event" /></div>
|
||||
<div class="right">
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/first-aid-list'">
|
||||
<input type="button" value="Erste-Hilfe-Liste (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/csv/participant-list'">
|
||||
<input type="button" value="Teili-Liste (CSV)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/kitchen-list'">
|
||||
<input type="button" value="Küchenübersicht (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/amount-list'">
|
||||
<input type="button" value="Beitragsliste (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/drinking-list'">
|
||||
<input type="button" value="Getränkeliste (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/photo-permission-list'">
|
||||
<input type="button" value="Foto-Erlaubnis (PDF)" />
|
||||
</a><br/>
|
||||
<input type="button" @click="sendPaymentReminder" class="fix-button" value="Zahlungserinnerung senden" /><br/>
|
||||
<input type="button" class="deny-button" value="Letzte Mahnung senden" style="display: none" /><br/>
|
||||
<input type="button" value="Rundmail senden" @click="mailToGroup" /><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th style="width: 200px">Veranstaltung</th>
|
||||
<td>{{ dynamicProps.event.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width: 200px">Beginn</th>
|
||||
<td>{{ dynamicProps.event.eventBegin }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Ende</th>
|
||||
<td>{{ dynamicProps.event.eventEnd }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Anmeldeschluss</th>
|
||||
<td>{{dynamicProps.event.earlyBirdEnd.formatted}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Nachmeldeschluss</th>
|
||||
<td>{{dynamicProps.event.registrationFinalEnd.formatted}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Anmelde-URL</th>
|
||||
<td>
|
||||
{{dynamicProps.event.url}}<br />
|
||||
<img :src="'/print-event-code/' + dynamicProps.event.identifier" alt="Event Code" style="width: 150px; height: 150px; margin-top: 20px;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="event-flexbox-row bottom">
|
||||
<label style="font-size: 9pt;" class="link" @click="showCommonSettings">Allgemeine Einstellungen</label>
|
||||
<label style="font-size: 9pt;" class="link" @click="showEventManagement">Veranstaltungsleitung</label>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal title="Veranstaltungsdetails" v-if="showEventData" :show="showEventData" @close="showEventData = false">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Beginn</th>
|
||||
<td>{{ dynamicProps.event.eventBegin }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Ende</th>
|
||||
<td>{{ dynamicProps.event.eventEnd }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Anmeldeschluss</th>
|
||||
<td>{{dynamicProps.event.earlyBirdEnd.formatted}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Nachmeldeschluss</th>
|
||||
<td>{{dynamicProps.event.registrationFinalEnd.formatted}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Anmelde-URL</th>
|
||||
<td>
|
||||
{{dynamicProps.event.url}}<br />
|
||||
<img :src="'/print-event-code/' + dynamicProps.event.identifier" alt="Event Code" style="width: 150px; height: 150px; margin-top: 20px;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</Modal>
|
||||
|
||||
<FullScreenModal
|
||||
:show="mailCompose"
|
||||
title="E-Mail senden"
|
||||
@close="mailCompose = false"
|
||||
>
|
||||
<MailCompose @closeComposer="mailCompose = false" :event="dynamicProps.event" mailToType="all" recipientIdentifier="Alle Teilnehmenden" />
|
||||
</FullScreenModal>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.event-flexbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 95%;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
.event-flexbox-row {
|
||||
display: flex;
|
||||
gap: 10px; /* Abstand zwischen den Spalten */
|
||||
}
|
||||
|
||||
.event-flexbox-row.top .left {
|
||||
flex: 0 0 calc(100% - 300px);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.event-flexbox-row.top .right {
|
||||
flex: 0 0 250px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.event-flexbox-row.bottom {
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.event-flexbox-row.top .right input[type="button"] {
|
||||
width: 100% !important;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
413
app/Domains/Event/Views/Partials/ParticipantData.vue
Normal file
413
app/Domains/Event/Views/Partials/ParticipantData.vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, watch} from "vue";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
|
||||
const staticProps = defineProps({
|
||||
editMode: Boolean,
|
||||
participant: Object,
|
||||
event: Object,
|
||||
});
|
||||
|
||||
const props = reactive({
|
||||
participant: staticProps.participant,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/event/participant/' + staticProps.participant.identifier + '/');
|
||||
const data = await response.json();
|
||||
Object.assign(props, data);
|
||||
});
|
||||
|
||||
|
||||
|
||||
const emit = defineEmits([
|
||||
'closeParticipantDetails',
|
||||
'markCocExisting',
|
||||
'paymentComplete',
|
||||
'cancelParticipation',
|
||||
'editParticipant',
|
||||
'saveParticipant',
|
||||
]);
|
||||
|
||||
const form = reactive({
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
nickname: '',
|
||||
address_1: '',
|
||||
address_2: '',
|
||||
postcode: '',
|
||||
city: '',
|
||||
localgroup: '',
|
||||
birthday: '',
|
||||
email_1: '',
|
||||
phone_1: '',
|
||||
contact_person: '',
|
||||
email_2: '',
|
||||
phone_2: '',
|
||||
arrival: '',
|
||||
departure: '',
|
||||
participationType: '',
|
||||
eatingHabit: '',
|
||||
allergies: '',
|
||||
intolerances: '',
|
||||
medications: '',
|
||||
extendedFirstAid: '',
|
||||
swimmingPermission: '',
|
||||
tetanusVaccination: '',
|
||||
notes: '',
|
||||
amountPaid: '',
|
||||
amountExpected: '',
|
||||
cocStatus: '',
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.participant,
|
||||
(participant) => {
|
||||
form.firstname = participant?.firstname ?? '';
|
||||
form.lastname = participant?.lastname ?? '';
|
||||
form.nickname = participant?.nickname ?? '';
|
||||
form.address_1 = participant?.address_1 ?? '';
|
||||
form.address_2 = participant?.address_2 ?? '';
|
||||
form.postcode = participant?.postcode ?? '';
|
||||
form.city = participant?.city ?? '';
|
||||
form.localgroup = participant?.local_group ?? '';
|
||||
form.birthday = participant?.birthdayDate ?? '';
|
||||
form.email_1 = participant?.email_1 ?? '';
|
||||
form.phone_1 = participant?.phone_1 ?? '';
|
||||
form.contact_person = participant?.contact_person ?? '';
|
||||
form.email_2 = participant?.email_2 ?? '';
|
||||
form.phone_2 = participant?.phone_2 ?? '';
|
||||
form.arrival = participant?.arrivalDate ?? '';
|
||||
form.departure = participant?.departureDate ?? '';
|
||||
form.participationType = participant?.participation_type ?? '';
|
||||
form.eatingHabit = participant?.eating_habit ?? '';
|
||||
form.allergies = participant?.allergies ?? '';
|
||||
form.intolerances = participant?.intolerances ?? '';
|
||||
form.medications = participant?.medications ?? '';
|
||||
form.extendedFirstAid = participant?.first_aid_permission ?? '';
|
||||
form.swimmingPermission = participant?.swimming_permission ?? '';
|
||||
form.tetanusVaccination = participant?.tetanus_vaccination ?? '';
|
||||
form.notes = participant?.notes ?? '';
|
||||
form.amountPaid = participant?.amountPaid.short ?? '';
|
||||
form.amountExpected = participant?.amountExpected.short ?? '';
|
||||
form.cocStatus = participant?.efz_status ?? '';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function markCocExisting(participant) {
|
||||
emit('markCocExisting', participant);
|
||||
close();
|
||||
}
|
||||
|
||||
function paymentComplete(participant) {
|
||||
emit('paymentComplete', participant);
|
||||
close();
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('closeParticipantDetails');
|
||||
}
|
||||
|
||||
function cancelParticipation(participant) {
|
||||
emit('cancelParticipation', participant);
|
||||
close();
|
||||
}
|
||||
|
||||
function enableEditMode() {
|
||||
emit('editParticipant');
|
||||
}
|
||||
|
||||
function saveParticipant() {
|
||||
emit('saveParticipant', { ...form });
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>Anmeldedetails</h2>
|
||||
|
||||
<div class="participationData">
|
||||
<div>
|
||||
<h3>Persönliche Daten</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">
|
||||
{{ props.participant.firstname }} {{ props.participant.lastname }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<input v-model="form.firstname" type="text" placeholder="Vorname" />
|
||||
<input v-model="form.lastname" type="text" placeholder="Nachname" />
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Pfadiname</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.nickname }}</span>
|
||||
<input v-else v-model="form.nickname" type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Anschrift</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">
|
||||
{{ props.participant.address_1 }}<br />
|
||||
{{ props.participant.address_2 }}<br />
|
||||
{{ props.participant.postcode }}
|
||||
{{ props.participant.city }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<input v-model="form.address_1" type="text" placeholder="Adresse 1" />
|
||||
<input v-model="form.address_2" type="text" placeholder="Adresse 2" />
|
||||
<input v-model="form.postcode" type="text" placeholder="PLZ" />
|
||||
<input v-model="form.city" type="text" placeholder="Ort" />
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Stamm</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.localgroup }}</span>
|
||||
<select v-else v-model="form.localgroup">
|
||||
<option v-for="group in staticProps.event.contributingLocalGroups" :key="group.id" :value="group.slug">{{ group.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Geburtsdatum</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.birthday }}</span>
|
||||
<input v-else v-model="form.birthday" type="date" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Kontaktdaten</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>E-Mail</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.email_1 }}</span>
|
||||
<input v-else v-model="form.email_1" type="email" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Telefon</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.phone_1 }}</span>
|
||||
<input v-else v-model="form.phone_1" type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Ansprechperson</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.contact_person }}</span>
|
||||
<input v-else v-model="form.contact_person" type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Ansprechperson E-Mail</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.email_2 }}</span>
|
||||
<input v-else v-model="form.email_2" type="email" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Ansprechperson Telefon</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.phone_2 }}</span>
|
||||
<input v-else v-model="form.phone_2" type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="participationData">
|
||||
<div>
|
||||
<h3>Teilnahmedetails</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Anreise</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.arrival }}</span>
|
||||
<input v-else v-model="form.arrival" type="date" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Abreise</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.departure }}</span>
|
||||
<input v-else v-model="form.departure" type="date" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teilnahmegruppe</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.participationType }}</span>
|
||||
<select v-else v-model="form.participationType">
|
||||
<option
|
||||
v-for="participationType in staticProps.event.participationTypes"
|
||||
:value="participationType.type.slug"
|
||||
>
|
||||
{{ participationType.type.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Ernährung</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.eatingHabit }}</span>
|
||||
<select v-else v-model="form.eatingHabit">
|
||||
<option
|
||||
v-for="eatingHabit in staticProps.event.eatingHabits"
|
||||
:value="eatingHabit.slug"
|
||||
>
|
||||
{{ eatingHabit.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>eFZ-Status</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.efzStatusReadable }}</span>
|
||||
<select v-else v-model="form.cocStatus">
|
||||
<option value="not_checked">Nicht geprüft</option>
|
||||
<option value="not_required">Nicht erforderlich</option>
|
||||
<option value="checked_valid">Geprüft und Vorhanden</option>
|
||||
<option value="checked_invalid">Nicht vorhanden</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Beitrag</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.amountPaid.readable }} / {{ props.participant.amountExpected.readable }}</span>
|
||||
<span v-else>
|
||||
<AmountInput v-model="form.amountPaid" style="width:74px" /> Euro
|
||||
/
|
||||
<AmountInput v-model="form.amountExpected" style="width: 74px" /> Euro
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Medizinische Informationen</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Allergien</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.allergies }}</span>
|
||||
<input type="text" v-else v-model="form.allergies" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Unverträglichkeiten</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.intolerances }}</span>
|
||||
<input type="text" v-else v-model="form.intolerances" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Medikamente</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.medications }}</span>
|
||||
<input type="text" v-else v-model="form.medications" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Erweiterte Erste Hilfe</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.extendedFirstAid }}</span>
|
||||
<select v-else v-model="form.extendedFirstAid">
|
||||
<option value="FIRST_AID_PERMISSION_ALLOWED">Erlaubt</option>
|
||||
<option value="FIRST_AID_PERMISSION_DENIED">Verweigert</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Badeerlaubnis</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.swimmingPermission }}</span>
|
||||
<select v-else v-model="form.swimmingPermission">
|
||||
<option value="SWIMMING_PERMISSION_ALLOWED">Vorhanden, kann schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_LIMITED">Vorhanden, kann nicht schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_DENIED">Verweigert</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Letzte Tetanus-Impfung</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.tetanusVaccination }}</span>
|
||||
<input v-else v-model="form.tetanusVaccination" type="date" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Anmerkungen</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.notes }}</span>
|
||||
<textarea v-else v-model="form.notes"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button v-if="!staticProps.editMode" class="button" @click="enableEditMode">Bearbeiten</button>
|
||||
<button v-else class="button" @click="saveParticipant">Speichern</button>
|
||||
|
||||
<button v-if="!props.participant.unregistered" class="button" @click="paymentComplete(props.participant)">Zahlung vollständig</button>
|
||||
<button v-if="!props.participant.unregistered" class="button" @click="markCocExisting(props.participant)">eFZ liegt vor</button>
|
||||
<button v-if="!props.participant.unregistered" class="button" @click="cancelParticipation(props.participant)">Abmelden</button>
|
||||
<button class="button" @click="close">Schließen</button>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.participationData {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.participationData div {
|
||||
flex: 1;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="date"],
|
||||
textarea
|
||||
{
|
||||
width: 250px
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 262px;
|
||||
}
|
||||
</style>
|
||||
538
app/Domains/Event/Views/Partials/ParticipantsList.vue
Normal file
538
app/Domains/Event/Views/Partials/ParticipantsList.vue
Normal file
@@ -0,0 +1,538 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import ParticipantData from "./ParticipantData.vue";
|
||||
import MailCompose from "./MailCompose.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import {format, getDay, getMonth, getYear} from "date-fns";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
localGroups: {},
|
||||
participants: {},
|
||||
event: {},
|
||||
listType: '',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const today = format(new Date(), "yyyy-MM-dd");
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
const searchTerms = reactive({});
|
||||
const selectedStatuses = reactive({});
|
||||
|
||||
const event = computed(() => props.data?.event ?? {});
|
||||
const participantGroups = computed(() => props.data?.participants ?? {});
|
||||
const showParticipantDetails = ref(false);
|
||||
const showParticipant = ref(null);
|
||||
const editMode = ref(false);
|
||||
|
||||
const mailCompose = ref(false);
|
||||
|
||||
const openCancelDialog = ref(false);
|
||||
const openPartialPaymentDialogSwitch = ref(false);
|
||||
|
||||
defineEmits(['showParticipantDetails', 'markCocExisting', 'paymentComplete'])
|
||||
|
||||
function openParticipantDetails(input) {
|
||||
showParticipantDetails.value = true;
|
||||
showParticipant.value = input;
|
||||
editMode.value = false;
|
||||
}
|
||||
|
||||
async function saveParticipant(formData) {
|
||||
if (!showParticipant.value?.identifier) {
|
||||
return;
|
||||
}
|
||||
const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/update', {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message ?? 'Die Änderungen wurden gespichert. Die Liste wird beim nächsten Neuladen neu generiert.');
|
||||
|
||||
document.getElementById('participant-' + data.participant.identifier + '-fullname').innerHTML = data.participant.fullname;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-birthday').innerText = data.participant.birthday;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-age').innerText = data.participant.age;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-localgroup').innerText = data.participant.localgroup;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-arrival').innerText = data.participant.arrival;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-departure').innerText = data.participant.departure;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-email_1').innerText = data.participant.email_1;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-email_2').innerText = data.participant.email_2 ?? '--';
|
||||
document.getElementById('participant-' + data.participant.identifier + '-phone_1').innerText = data.participant.phone_1;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-phone_2').innerText = data.participant.phone_2 ?? '--';
|
||||
|
||||
if (data.cocChanged) {
|
||||
document.getElementById('participant-' + data.identifier + '-coc-status').innerText = data.coc.statusText;
|
||||
document.getElementById('participant-' + data.identifier + '-coc-action').style.display=data.coc.action;
|
||||
document.getElementById('participant-' + data.identifier + '-name').className = data.coc.class;
|
||||
}
|
||||
|
||||
if (data.amountChanged) {
|
||||
document.getElementById('participant-' + data.identifier + '-payment').removeAttribute('class');
|
||||
document.getElementById('participant-' + data.identifier + '-paid').innerText = data.amount.paid;
|
||||
document.getElementById('participant-' + data.identifier + '-expected').innerText = data.amount.expected;
|
||||
document.getElementById('participant-' + data.identifier + '-actions').style.display=data.amount.actions;
|
||||
document.getElementById('participant-' + data.identifier + '-payment').className = data.amount.class;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
editMode.value = false;
|
||||
} else {
|
||||
toast.error(data.message ?? 'Speichern fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getGroupEntries = computed(() => {
|
||||
return Object.entries(participantGroups.value ?? {});
|
||||
});
|
||||
|
||||
const getAgeCounts = (participants) => {
|
||||
const buckets = {
|
||||
'0-5': 0,
|
||||
'6-11': 0,
|
||||
'12-15': 0,
|
||||
'16-17': 0,
|
||||
'18-27': 0,
|
||||
'27+': 0,
|
||||
};
|
||||
|
||||
(participants ?? []).forEach((participant) => {
|
||||
const age = Number(participant?.age);
|
||||
|
||||
if (!Number.isFinite(age)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (age >= 0 && age <= 5) {
|
||||
buckets['0-5']++;
|
||||
} else if (age <= 11) {
|
||||
buckets['6-11']++;
|
||||
} else if (age <= 15) {
|
||||
buckets['12-15']++;
|
||||
} else if (age <= 17) {
|
||||
buckets['16-17']++;
|
||||
} else if (age <= 27) {
|
||||
buckets['18-27']++;
|
||||
} else {
|
||||
buckets['27+']++;
|
||||
}
|
||||
});
|
||||
|
||||
return buckets;
|
||||
};
|
||||
|
||||
const getSearchText = (participant) => {
|
||||
return [
|
||||
participant?.firstname,
|
||||
participant?.lastname,
|
||||
participant?.nickname,
|
||||
participant?.email_1,
|
||||
participant?.email_2,
|
||||
participant?.phone_1,
|
||||
participant?.phone_2,
|
||||
participant?.local_group_string,
|
||||
participant?.participation_type_string,
|
||||
participant?.efz_status_string,
|
||||
participant?.amount_string,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
const getFilteredParticipants = (groupKey, participants) => {
|
||||
const searchTerm = (searchTerms[groupKey] ?? "").trim().toLowerCase();
|
||||
|
||||
return (participants ?? []).filter((participant) => {
|
||||
const matchesSearch =
|
||||
!searchTerm ||
|
||||
getSearchText(participant).includes(searchTerm);
|
||||
|
||||
|
||||
return matchesSearch;
|
||||
});
|
||||
};
|
||||
|
||||
const getRowClass = (participant) => {
|
||||
if (participant?.unregistered_at) {
|
||||
return "bg-gray-50 text-gray-500";
|
||||
}
|
||||
|
||||
if (
|
||||
Number(participant?.amount?.amount ?? participant?.amount ?? 0) !==
|
||||
Number(participant?.amount_paid?.amount ?? participant?.amount_paid ?? 0)
|
||||
) {
|
||||
return "bg-red-50";
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
async function paymentComplete(participant) {
|
||||
const data = await request('/api/v1/event/participant/' + participant.identifier + '/payment-complete', {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
document.getElementById('participant-' + participant.identifier + '-payment').removeAttribute('class');
|
||||
document.getElementById('participant-' + participant.identifier + '-paid').innerText = participant.amountExpected.readable;
|
||||
document.getElementById('participant-' + participant.identifier + '-actions').style.display='none';
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function markCocExisting(participant) {
|
||||
const data = await request('/api/v1/event/participant/' + participant.identifier + '/mark-coc-existing', {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
document.getElementById('participant-' + participant.identifier + '-coc-status').innerText = 'Gültig';
|
||||
document.getElementById('participant-' + participant.identifier + '-coc-action').style.display='none';
|
||||
document.getElementById('participant-' + participant.identifier + '-name').removeAttribute('class');
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function openCancelParticipationDialog(participant) {
|
||||
showParticipant.value = participant;
|
||||
openCancelDialog.value = true;
|
||||
}
|
||||
|
||||
async function execCancelParticipation() {
|
||||
const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/signoff', {
|
||||
method: "POST",
|
||||
body: {
|
||||
cancel_date: document.getElementById('cancel_date').value,
|
||||
},
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
|
||||
document.getElementById('participant-' + data.identifier + '-common').style.display = 'none';
|
||||
document.getElementById('participant-' + data.identifier + '-meta').style.display = 'none';
|
||||
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
|
||||
}
|
||||
openCancelDialog.value = false;
|
||||
}
|
||||
|
||||
async function execResignonParticipant(participant) {
|
||||
const data = await request('/api/v1/event/participant/' + participant.identifier + '/re-signon', {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
|
||||
document.getElementById('participant-' + data.identifier + '-common').style.display = 'none';
|
||||
document.getElementById('participant-' + data.identifier + '-meta').style.display = 'none';
|
||||
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
|
||||
}
|
||||
openCancelDialog.value = false;
|
||||
}
|
||||
|
||||
function openPartialPaymentDialog(participant) {
|
||||
showParticipant.value = participant;
|
||||
openPartialPaymentDialogSwitch.value = true;
|
||||
}
|
||||
|
||||
async function execPartialPayment() {
|
||||
const data = await request('/api/v1/event/participant/' + showParticipant.value.identifier + '/partial-payment', {
|
||||
method: "POST",
|
||||
body: {
|
||||
amount: document.getElementById('partial_payment_amount').value,
|
||||
},
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
|
||||
document.getElementById('participant-' + data.identifier + '-payment').removeAttribute('class');
|
||||
document.getElementById('participant-' + data.identifier + '-paid').innerText = data.amount.paid;
|
||||
document.getElementById('participant-' + data.identifier + '-expected').innerText = data.amount.expected;
|
||||
document.getElementById('participant-' + data.identifier + '-actions').style.display=data.amount.actions;
|
||||
document.getElementById('participant-' + data.identifier + '-payment').className = data.amount.class;
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
|
||||
}
|
||||
|
||||
openPartialPaymentDialogSwitch.value = false;
|
||||
}
|
||||
|
||||
const mailToType = ref('')
|
||||
const recipientIdentifier = ref('')
|
||||
|
||||
function mailToGroup(groupKey) {
|
||||
recipientIdentifier.value = groupKey;
|
||||
mailToType.value = props.data.listType;
|
||||
mailCompose.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>{{ event?.name ?? "Veranstaltung" }}</h2>
|
||||
<div :key="groupKey">
|
||||
<div>
|
||||
<table style="width: 95%; margin: 20px auto; border-collapse: collapse;" v-for="[groupKey, participants] in getGroupEntries">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" style="background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold">
|
||||
{{ groupKey }} ({{ participants.length }} Personen)
|
||||
</th>
|
||||
</tr>
|
||||
<tr style="background: linear-gradient(to bottom, #fff, #f6f7f7);">
|
||||
<th>Name</th>
|
||||
<th>Beitrag</th>
|
||||
<th>E-Mail-Adresse</th>
|
||||
<th>Telefon</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<template
|
||||
v-for="participant in getFilteredParticipants(groupKey, participants)"
|
||||
:key="participant.id"
|
||||
>
|
||||
<tr :class="getRowClass(participant)" :id="'participant-' + participant.identifier + '-common'">
|
||||
<td :id="'participant-' + participant.identifier +'-name'"
|
||||
style="width: 300px;"
|
||||
:class="participant.efz_status === 'checked_invalid' ? 'efz-invalid' :
|
||||
participant.efz_status === 'not_checked' ? 'efz-not-checked' : ''">
|
||||
<div :id="'participant-' + participant.identifier +'-fullname'" v-html="participant.fullname" /><br />
|
||||
Geburtsdatum: <label :id="'participant-' + participant.identifier +'-birthday'">{{ participant.birthday }}</label><br />
|
||||
Alter: <label :id="'participant-' + participant.identifier +'-age'">{{ participant.age }}</label> Jahre<br />
|
||||
|
||||
|
||||
<span>eFZ-Status: <label :id="'participant-' + participant.identifier +'-coc-status'">{{ participant.efzStatusReadable }}</label></span>
|
||||
<span :id="'participant-' + participant.identifier +'-coc-action'" v-if="participant.efz_status !== 'checked_valid' && participant.efz_status !== 'not_required'" class="link" style="color: #3cb62e; font-size: 11pt;" @click="markCocExisting(participant)">Vorhanden?</span>
|
||||
</td>
|
||||
|
||||
<td :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !participant.unregistered ? 'not-paid' : ''" style="width: 275px; '">
|
||||
Gezahlt: <label :id="'participant-' + participant.identifier + '-paid'">{{ participant?.amountPaid.readable }}</label> /<br />
|
||||
Gesamt: <label :id="'participant-' + participant.identifier + '-expected'">{{ participant?.amountExpected.readable }}</label>
|
||||
<br /><br />
|
||||
<span v-if="participant.amount_left_value != 0 && !participant.unregistered" :id="'participant-' + participant.identifier + '-actions'">
|
||||
<span class="link" style="font-size:10pt;" @click="paymentComplete(participant)">Zahlung buchen</span>
|
||||
<span class="link" style="font-size:10pt;" @click="openPartialPaymentDialog(participant)">Teilzahlung buchen</span>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label :id="'participant-' + participant.identifier +'-email_1'" class="block-label">{{ participant?.email_1 ?? "-" }}</label>
|
||||
<label :id="'participant-' + participant.identifier +'-email_2'" class="block-label">{{ participant.email_2 }}</label>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label :id="'participant-' + participant.identifier +'-phone_1'" class="block-label">{{ participant?.phone_1 }}</label>
|
||||
<label :id="'participant-' + participant.identifier +'-phone_2'" class="block-label">{{ participant?.phone_2 }}</label>
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
<tr class="participant-meta-row" :id="'participant-' + participant.identifier + '-meta'">
|
||||
<td colspan="5" style="height: 15px !important; font-size: 9pt; background-color: #ffffff; border-top-style: none;">
|
||||
<label :id="'participant-' + participant.identifier +'-localgroup'">
|
||||
{{ participant?.localgroup ?? "-" }}
|
||||
</label> |
|
||||
<strong> Anreise: </strong>
|
||||
<label :id="'participant-' + participant.identifier +'-arrival'">
|
||||
{{ participant?.arrival ?? "-" }}
|
||||
</label>|
|
||||
<strong> Abreise: </strong>
|
||||
<label :id="'participant-' + participant.identifier +'-departure'">
|
||||
{{ participant?.departure ?? "-" }}
|
||||
</label> |
|
||||
<label v-if="!participant.unregistered">
|
||||
<strong> Angemeldet am: </strong>{{ participant?.registerDate ?? "-" }}
|
||||
</label>
|
||||
<label v-else>
|
||||
<strong> Abgemeldet am: </strong>{{ participant?.unregisteredAt ?? "-" }}
|
||||
</label> |
|
||||
<span class="link" @click="openParticipantDetails(participant)">Details</span> |
|
||||
<span class="link">E-Mail senden</span> |
|
||||
<span @click="openCancelParticipationDialog(participant)" v-if="!participant.unregistered" class="link" style="color: #da7070;">Abmelden</span>
|
||||
<span v-else class="link" @click="execResignonParticipant(participant)" style="color: #3cb62e;">Wieder anmelden</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<tr>
|
||||
<td colspan="3" style="font-weight: normal;">
|
||||
0 - 5 Jahre: <strong>{{ getAgeCounts(participants)['0-5'] ?? 0 }}</strong> |
|
||||
6-11 Jahre: <strong>{{ getAgeCounts(participants)['6-11'] ?? 0 }}</strong> |
|
||||
12-15 Jahre: <strong>{{ getAgeCounts(participants)['12-15'] ?? 0 }}</strong> |
|
||||
16 - 17 Jahre: <strong>{{ getAgeCounts(participants)['16-17'] ?? 0 }}</strong> |
|
||||
18 - 27 Jahre: <strong>{{ getAgeCounts(participants)['18-27'] ?? 0 }}</strong> |
|
||||
27 Jahre und älter: <strong>{{ getAgeCounts(participants)['27+'] ?? 0 }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<input type="button" class="button" @click="mailToGroup(groupKey)" value="E-Mail an Gruppe senden" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
:show="showParticipantDetails"
|
||||
title="Anmeldedetails ansehen"
|
||||
@close="showParticipantDetails = false;"
|
||||
>
|
||||
<ParticipantData
|
||||
@cancelParticipation="openCancelParticipationDialog"
|
||||
:participant="showParticipant"
|
||||
:editMode="editMode"
|
||||
:event="event"
|
||||
@editParticipant="editMode = true"
|
||||
@saveParticipant="saveParticipant"
|
||||
@paymentComplete="paymentComplete"
|
||||
@markCocExisting="markCocExisting"
|
||||
@closeParticipantDetails="showParticipantDetails = false" />
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
:show="openCancelDialog"
|
||||
title="Anmeldung stornieren"
|
||||
width="350px"
|
||||
@close="openCancelDialog = false"
|
||||
>
|
||||
Datum der Abmeldung:
|
||||
<input type="date" style="margin-top: 10px;" id="cancel_date" :value="today" />
|
||||
<br /><br />
|
||||
<button class="button" @click="execCancelParticipation()">Abmeldung durchführen</button>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
:show="openPartialPaymentDialogSwitch"
|
||||
title="Teilbetragszahlung"
|
||||
width="350px"
|
||||
@close="openPartialPaymentDialogSwitch = false"
|
||||
>
|
||||
Gesamtbetrag der Zahlung:
|
||||
<AmountInput type="text" v-model="showParticipant.amountExpected.short" style="margin-top: 10px; width: 100px !important;" id="partial_payment_amount" /> Euro /
|
||||
{{showParticipant.amountExpected.readable}} Euro
|
||||
<br /><br />
|
||||
<button class="button" @click="execPartialPayment()">Teilbetrag buchen</button>
|
||||
</Modal>
|
||||
|
||||
<FullScreenModal
|
||||
:show="mailCompose"
|
||||
title="E-Mail senden"
|
||||
@close="mailCompose = false"
|
||||
>
|
||||
<MailCompose @closeComposer="mailCompose = false" :event="event" :mailToType="mailToType" :recipientIdentifier="recipientIdentifier" />
|
||||
</FullScreenModal>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
margin-bottom: 60px !important;
|
||||
}
|
||||
|
||||
tr {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr td {
|
||||
height: 80px;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
font-size: 11pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
tr th {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr th:after {
|
||||
content: "";
|
||||
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
border-style: solid;
|
||||
border-width: 0 1px;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
border-style: solid;
|
||||
border-width: 5px 1px 0 1px;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
border-width: 1px 1px 0 1px;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-width: 0 1px 1px 1px;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
font-size: 10pt;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.not-paid {
|
||||
color: #ff0000; background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.efz-invalid {
|
||||
color: #ff0000; background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.efz-not-checked {
|
||||
color: #8D914BFF; background-color: #F4E99EFF;
|
||||
}
|
||||
|
||||
.block-label {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
295
app/Domains/Event/Views/Partials/ParticipationFees.vue
Normal file
295
app/Domains/Event/Views/Partials/ParticipationFees.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<script setup>
|
||||
import {reactive, watch} from "vue";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
const formData = reactive({
|
||||
"pft_1_active": true,
|
||||
"pft_1_amount_standard": props.event.participationFee_1.amount_standard_edit,
|
||||
"pft_1_amount_reduced": props.event.participationFee_1.amount_reduced_edit,
|
||||
"pft_1_amount_solidarity": props.event.participationFee_1.amount_solidarity_edit,
|
||||
"pft_1_description": props.event.participationFee_1.description,
|
||||
|
||||
"pft_2_active": props.event.participationFee_2.active,
|
||||
"pft_2_amount_standard": props.event.participationFee_2.amount_standard_edit,
|
||||
"pft_2_amount_reduced": props.event.participationFee_2.amount_reduced_edit,
|
||||
"pft_2_amount_solidarity": props.event.participationFee_2.amount_solidarity_edit,
|
||||
"pft_2_description": props.event.participationFee_2.description,
|
||||
|
||||
"pft_3_active": props.event.participationFee_3.active,
|
||||
"pft_3_amount_standard": props.event.participationFee_3.amount_standard_edit,
|
||||
"pft_3_amount_reduced": props.event.participationFee_3.amount_reduced_edit,
|
||||
"pft_3_amount_solidarity": props.event.participationFee_3.amount_solidarity_edit,
|
||||
"pft_3_description": props.event.participationFee_3.description,
|
||||
|
||||
"pft_4_active": props.event.participationFee_4.active,
|
||||
"pft_4_amount_standard": props.event.participationFee_4.amount_standard_edit,
|
||||
"pft_4_amount_reduced": props.event.participationFee_4.amount_reduced_edit,
|
||||
"pft_4_amount_solidarity": props.event.participationFee_4.amount_solidarity_edit,
|
||||
"pft_4_description": props.event.participationFee_4.description,
|
||||
|
||||
'maxAmount': props.event.maxAmount,
|
||||
'sibling_reduction': props.event.siblingReduction,
|
||||
|
||||
})
|
||||
|
||||
function validateInput() {
|
||||
var noErrors = true;
|
||||
|
||||
if (formData.pft_1_description === '') {
|
||||
errors.pft_1_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
if (formData.pft_2_description === '' && formData.pft_2_active) {
|
||||
errors.pft_2_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
if (formData.pft_3_description === '' && formData.pft_3_active) {
|
||||
errors.pft_3_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
if (formData.pft_4_description === '' && formData.pft_4_active) {
|
||||
errors.pft_4_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
return noErrors;
|
||||
|
||||
}
|
||||
|
||||
async function saveParticipationFees() {
|
||||
if (!validateInput()) {
|
||||
toast.error('Bitte prüfe alle Eingaben auf Fehler')
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await request('/api/v1/event/details/' + props.event.id + '/participation-fees', {
|
||||
method: "POST",
|
||||
body: {
|
||||
event_id: props.event.id,
|
||||
pft_1_active: formData.pft_1_active,
|
||||
pft_1_amount_standard: formData.pft_1_amount_standard,
|
||||
pft_1_amount_reduced: formData.pft_1_amount_reduced,
|
||||
pft_1_amount_solidarity: formData.pft_1_amount_solidarity,
|
||||
pft_1_description: formData.pft_1_description,
|
||||
|
||||
pft_2_active: formData.pft_2_active,
|
||||
pft_2_amount_standard: formData.pft_2_amount_standard,
|
||||
pft_2_amount_reduced: formData.pft_2_amount_reduced,
|
||||
pft_2_amount_solidarity: formData.pft_2_amount_solidarity,
|
||||
pft_2_description: formData.pft_2_description,
|
||||
|
||||
pft_3_active: formData.pft_3_active,
|
||||
pft_3_amount_standard: formData.pft_3_amount_standard,
|
||||
pft_3_amount_reduced: formData.pft_3_amount_reduced,
|
||||
pft_3_amount_solidarity: formData.pft_3_amount_solidarity,
|
||||
pft_3_description: formData.pft_3_description,
|
||||
|
||||
pft_4_active: formData.pft_4_active,
|
||||
pft_4_amount_standard: formData.pft_4_amount_standard,
|
||||
pft_4_amount_reduced: formData.pft_4_amount_reduced,
|
||||
pft_4_amount_solidarity: formData.pft_4_amount_solidarity,
|
||||
pft_4_description: formData.pft_4_description,
|
||||
|
||||
maxAmount: formData.maxAmount,
|
||||
sibling_reduction: formData.sibling_reduction,
|
||||
}
|
||||
})
|
||||
|
||||
toast.success('Die Teilnahmekonditionen wurden gespeichert')
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function recalculateMaxAmount(newValue) {
|
||||
if (formData.maxAmount === 0) return;
|
||||
|
||||
var newAmount = parseFloat(newValue.replace(',', '.'));
|
||||
if (props.event.payPerDay) {
|
||||
newAmount = newAmount * props.event.duration;
|
||||
}
|
||||
|
||||
var currentMaxAmount = formData.maxAmount.replace(',', '.');
|
||||
|
||||
if (newAmount > currentMaxAmount) {
|
||||
formData.maxAmount = newAmount.toFixed(2).replace('.', ',');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td><h4>Aktiv</h4></td>
|
||||
<td><h4>Preisgruppe</h4></td>
|
||||
<td v-if="!props.event.solidarityPayment"><h4>Betrag</h4></td>
|
||||
<td v-else><h4>Regulärer Beitrag</h4></td>
|
||||
<td v-if="props.event.solidarityPayment"><h4>Reduzierter Beitrag</h4></td>
|
||||
<td v-if="props.event.solidarityPayment"><h4>Solidaritätsbeitrag</h4></td>
|
||||
<td><h4>Beschreibung</h4></td>
|
||||
</tr>
|
||||
<tr style="height: 65px; vertical-align: top">
|
||||
<td>
|
||||
<input type="checkbox" v-model="formData.participationFeeType_1" checked disabled/>
|
||||
</td>
|
||||
<td>
|
||||
Teilnehmende
|
||||
</td>
|
||||
<td>
|
||||
<AmountInput v-model="formData.pft_1_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
<AmountInput v-model="formData.pft_1_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment">
|
||||
<AmountInput v-model="formData.pft_1_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" v-model="formData.pft_1_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_1_description" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="height: 65px; vertical-align: top;">
|
||||
<td>
|
||||
<input id="use_pft_2" type="checkbox" v-model="formData.pft_2_active" :checked="formData.pft_2_active" />
|
||||
</td>
|
||||
<td>
|
||||
<label for="use_pft_2" style="cursor: default">
|
||||
Kernteam
|
||||
</label>
|
||||
</td>
|
||||
<td v-if="formData.pft_2_active">
|
||||
<AmountInput v-model="formData.pft_2_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_2_active">
|
||||
<AmountInput v-model="formData.pft_2_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_2_active">
|
||||
<AmountInput v-model="formData.pft_2_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
|
||||
<td v-if="formData.pft_2_active">
|
||||
<input type="text" v-model="formData.pft_2_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_2_description" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="height: 65px; vertical-align: top;">
|
||||
<td>
|
||||
<input id="use_pft_3" type="checkbox" v-model="formData.pft_3_active" :checked="formData.pft_3_active" />
|
||||
</td>
|
||||
<td>
|
||||
<label for="use_pft_3" style="cursor: default">
|
||||
Unterstützende
|
||||
</label>
|
||||
</td>
|
||||
<td v-if="formData.pft_3_active">
|
||||
<AmountInput v-model="formData.pft_3_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_3_active">
|
||||
<AmountInput v-model="formData.pft_3_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_3_active">
|
||||
<AmountInput v-model="formData.pft_3_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="formData.pft_3_active">
|
||||
<input type="text" v-model="formData.pft_3_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_3_description" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="height: 65px; vertical-align: top;">
|
||||
<td>
|
||||
<input id="use_pft_4" type="checkbox" v-model="formData.pft_4_active" :checked="formData.pft_4_active" />
|
||||
</td>
|
||||
<td>
|
||||
<label for="use_pft_4" style="cursor: default">
|
||||
Sonstige
|
||||
</label>
|
||||
</td>
|
||||
<td v-if="formData.pft_4_active">
|
||||
<AmountInput v-model="formData.pft_4_amount_standard" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount_standard)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_4_active">
|
||||
<AmountInput v-model="formData.pft_4_amount_reduced" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount_reduced)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.solidarityPayment && formData.pft_4_active">
|
||||
<AmountInput v-model="formData.pft_4_amount_solidarity" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount_solidarity)" />
|
||||
<label style="font-size: 10pt;" v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||
<label style="font-size: 10pt;" v-else> Euro Gesamt</label>
|
||||
</td>
|
||||
|
||||
<td v-if="formData.pft_4_active">
|
||||
<input type="text" v-model="formData.pft_4_description" style="width: 300px;" />
|
||||
<ErrorText :message="errors.pft_4_description" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
Maximaler Beitrag für Veranstaltung:
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<AmountInput v-model="formData.maxAmount" class="width-small" /> Euro Gesamt
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="checkbox" v-model="formData.sibling_reduction" id="sibling_reduction" :checked="formData.sibling_reduction" />
|
||||
<label for="sibling_reduction">50% Preisnachlass für Geschwisterkinder gewähren</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" style="padding-top: 20px;">
|
||||
<input type="button" value="Speichern" @click="saveParticipationFees" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
165
app/Domains/Event/Views/Partials/ParticipationSummary.vue
Normal file
165
app/Domains/Event/Views/Partials/ParticipationSummary.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
event: Object
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Übersicht</h2>
|
||||
<div class="participant-flexbox">
|
||||
<div class="participant-flexbox-row top">
|
||||
<div class="left">
|
||||
<h3>Teilnehmende</h3>
|
||||
<table class="participant-income-table" style="margin-bottom: 40px; font-size: 11pt;">
|
||||
<tr>
|
||||
<th>Teili</th>
|
||||
<td><strong>{{props.event.participants.participant.count}} Personen:</strong></td>
|
||||
<td>
|
||||
{{props.event.participants.participant.amount.paid.readable}} /
|
||||
</td>
|
||||
<td>
|
||||
{{props.event.participants.participant.amount.expected.readable}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Team</th>
|
||||
<td><strong>{{props.event.participants.team.count}} Personen:</strong></td>
|
||||
<td>
|
||||
{{props.event.participants.team.amount.paid.readable}} /
|
||||
</td>
|
||||
<td>
|
||||
{{props.event.participants.team.amount.expected.readable}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Unterstützende</th>
|
||||
<td><strong>{{props.event.participants.volunteer.count}} Personen:</strong></td>
|
||||
<td>
|
||||
{{props.event.participants.volunteer.amount.paid.readable}} /
|
||||
</td>
|
||||
<td>
|
||||
{{props.event.participants.volunteer.amount.expected.readable}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Sonstige</th>
|
||||
<td><strong>{{props.event.participants.other.count}} Personen:</strong></td>
|
||||
<td>
|
||||
{{props.event.participants.other.amount.paid.readable}} /
|
||||
</td>
|
||||
<td>
|
||||
{{props.event.participants.other.amount.expected.readable}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th colspan="2">Sonstige Einnahmen</th>
|
||||
<td colspan="2">{{ props.event.flatSupport }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th style="padding-bottom: 20px" colspan="2">Förderung</th>
|
||||
<td style="padding-bottom: 20px" colspan="2">
|
||||
{{ props.event.supportPerson.readable }}<br />
|
||||
<label style="font-size: 9pt;">({{ props.event.supportPersonIndex }} / Tag p.P.)</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<th colspan="2" style="border-width: 1px; border-bottom-style: solid">Gesamt</th>
|
||||
<td style="font-weight: bold; border-width: 1px; border-bottom-style: solid">
|
||||
{{ props.event.income.real.readable }} /
|
||||
</td>
|
||||
|
||||
<td style="font-weight: bold; border-width: 1px; border-bottom-style: solid">
|
||||
{{ props.event.income.expected.readable }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th style="padding-top: 20px; font-size: 12pt !important;" colspan="2">Bilanz</th>
|
||||
<td v-if="props.event.totalBalance.real.value >= 0" style="color: #4caf50;font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
|
||||
{{ props.event.totalBalance.real.readable }} /
|
||||
</td>
|
||||
<td v-else style="color: #f44336; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
|
||||
{{props.event.totalBalance.real.readable}}
|
||||
</td>
|
||||
|
||||
<td v-if="props.event.totalBalance.expected.value >= 0" style="color: #4caf50; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
|
||||
{{ props.event.totalBalance.expected.readable }}
|
||||
</td>
|
||||
<td v-else style="color: #f44336; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
|
||||
{{props.event.totalBalance.expected.readable}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<h3>Ausgaben</h3>
|
||||
<table class="event-payment-table" style="font-size: 10pt;">
|
||||
<tr v-for="amount in props.event.costUnit.amounts">
|
||||
<th>{{amount.name}}</th>
|
||||
<td>{{amount.string}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 58px">Gesamt</th>
|
||||
<td style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 58px; font-weight: bold">{{props.event.costUnit.overAllAmount.text}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.participant-flexbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 95%;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
.participant-flexbox-row {
|
||||
display: flex;
|
||||
gap: 10px; /* Abstand zwischen den Spalten */
|
||||
}
|
||||
|
||||
.participant-flexbox-row.top .left {
|
||||
flex: 0 0 50%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.participant-flexbox.top .right {
|
||||
flex: 0 0 50%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.participant-income-table,
|
||||
.event-payment-table {
|
||||
width: 475px;
|
||||
}
|
||||
|
||||
.participant-income-table th {
|
||||
width: 20px;
|
||||
font-size: 11pt !important;
|
||||
}
|
||||
|
||||
.participant-income-table tr td:first-child {
|
||||
width: 25px !important;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.event-payment-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
152
app/Domains/Event/Views/Partials/SignUpForm/SignupForm.vue
Normal file
152
app/Domains/Event/Views/Partials/SignUpForm/SignupForm.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script setup>
|
||||
import { useSignupForm } from './composables/useSignupForm.js'
|
||||
import StepAge from './steps/StepAge.vue'
|
||||
import StepContactPerson from './steps/StepContactPerson.vue'
|
||||
import StepPersonalData from './steps/StepPersonalData.vue'
|
||||
import StepRegistrationMode from './steps/StepRegistrationMode.vue'
|
||||
import StepArrival from './steps/StepArrival.vue'
|
||||
import StepAddons from './steps/StepAddons.vue'
|
||||
import StepPhotoPermissions from './steps/StepPhotoPermissions.vue'
|
||||
import StepAllergies from './steps/StepAllergies.vue'
|
||||
import StepSummary from './steps/StepSummary.vue'
|
||||
import SubmitSuccess from './after-submit/SubmitSuccess.vue'
|
||||
import SubmitAlreadyExists from './after-submit/SubmitAlreadyExists.vue'
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
participantData: Object,
|
||||
localGroups: Array,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['registrationDone'])
|
||||
|
||||
const {
|
||||
currentStep, goToStep, formData, selectedAddons,
|
||||
submit, submitting, submitResult, summaryLoading, summaryAmount
|
||||
} = useSignupForm(props.event, props.participantData)
|
||||
|
||||
const steps = [
|
||||
{ step: 1, label: 'Alter' },
|
||||
{ step: 2, label: 'Kontaktperson' },
|
||||
{ step: 3, label: 'Persönliche Daten' },
|
||||
{ step: 4, label: 'An-/Abreise' },
|
||||
{ step: 5, label: 'Teilnahmegruppe' },
|
||||
{ step: 6, label: 'Zusatzoptionen' },
|
||||
{ step: 7, label: 'Fotoerlaubnis' },
|
||||
{ step: 8, label: 'Allergien' },
|
||||
{ step: 9, label: 'Zusammenfassung' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Nach Submit -->
|
||||
<SubmitSuccess
|
||||
v-if="submitResult?.status === 'success'"
|
||||
:participant="submitResult?.participant"
|
||||
:event="event"
|
||||
/>
|
||||
<SubmitAlreadyExists v-else-if="submitResult?.status === 'exists'" :event="event" />
|
||||
|
||||
<template v-else>
|
||||
<!-- Fortschrittsleiste (ab Step 2) -->
|
||||
<div v-if="currentStep > 1" style="margin-bottom: 28px;">
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; align-items: center;">
|
||||
<template v-for="(s, index) in steps.filter(s => s.step > 1)" :key="s.step">
|
||||
<!-- Trennlinie zwischen Pills -->
|
||||
<div v-if="index > 0" style="flex-shrink: 0; width: 16px; height: 2px; background: #e5e7eb; border-radius: 1px;"></div>
|
||||
<div
|
||||
:style="{
|
||||
padding: '5px 14px',
|
||||
borderRadius: '999px',
|
||||
fontSize: '0.78rem',
|
||||
fontWeight: '600',
|
||||
whiteSpace: 'nowrap',
|
||||
border: '2px solid',
|
||||
borderColor: currentStep === s.step ? '#2563eb' : currentStep > s.step ? '#bbf7d0' : '#e5e7eb',
|
||||
background: currentStep === s.step ? '#2563eb' : currentStep > s.step ? '#f0fdf4' : '#f9fafb',
|
||||
color: currentStep === s.step ? 'white' : currentStep > s.step ? '#15803d' : '#9ca3af',
|
||||
cursor: currentStep > s.step ? 'pointer' : 'default',
|
||||
}"
|
||||
@click="currentStep > s.step ? goToStep(s.step) : null"
|
||||
>
|
||||
<span v-if="currentStep > s.step" style="margin-right: 4px;">✓</span>
|
||||
{{ s.label }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Fortschrittsbalken -->
|
||||
<div style="margin-top: 10px; height: 3px; background: #e5e7eb; border-radius: 2px; overflow: hidden;">
|
||||
<div
|
||||
:style="{
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, #2563eb, #3b82f6)',
|
||||
borderRadius: '2px',
|
||||
width: ((currentStep - 2) / (steps.length - 2) * 100) + '%',
|
||||
transition: 'width 0.3s ease',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Steps -->
|
||||
<form @submit.prevent="submit">
|
||||
<StepAge v-if="currentStep === 1" :event="event" @next="goToStep" />
|
||||
<StepContactPerson v-if="currentStep === 2" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepPersonalData v-if="currentStep === 3" :formData="formData" :localGroups="localGroups" @next="goToStep" @back="goToStep" />
|
||||
<StepArrival v-if="currentStep === 4" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepRegistrationMode v-if="currentStep === 5" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepAddons v-if="currentStep === 6" :formData="formData" :event="event" :selectedAddons="selectedAddons" @next="goToStep" @back="goToStep" />
|
||||
<StepPhotoPermissions v-if="currentStep === 7" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepAllergies v-if="currentStep === 8" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepSummary
|
||||
v-if="currentStep === 9"
|
||||
:formData="formData"
|
||||
:event="event"
|
||||
:summaryAmount="summaryAmount"
|
||||
:summaryLoading="summaryLoading"
|
||||
:submitting="submitting"
|
||||
@back="goToStep"
|
||||
@submit="submit"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.form-table { width: 100%; border-collapse: collapse; }
|
||||
.form-table td { padding: 8px 12px 8px 0; vertical-align: top; }
|
||||
.form-table td:first-child { width: 220px; color: #374151; font-weight: 500; }
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="date"],
|
||||
.form-table select,
|
||||
.form-table textarea {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.btn-row { display: flex; gap: 10px; padding-top: 16px; }
|
||||
.btn-primary {
|
||||
padding: 8px 20px;
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn-secondary {
|
||||
padding: 8px 20px;
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user