Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 998a799c3a | |||
| ef8f3ebe6c | |||
| 92976fbf27 | |||
| e7e7f039b8 | |||
| 0d436d8190 | |||
| 3fdbaf0285 | |||
| 8a049efe49 | |||
| a62b2214c4 | |||
| f00e665a03 | |||
| 454d83de2e | |||
| 30cc0b79c5 | |||
| 775d9158a6 | |||
| c60429ad28 | |||
| 0cf9602958 | |||
| e2fb616565 | |||
| faaf12b6a4 | |||
| 1a69a20750 | |||
| 71cb09f051 | |||
| b668195e21 | |||
| 94934027fa | |||
| 120c8873ef | |||
| 188735e4aa | |||
| b291c05da7 | |||
| adffa6d6b2 | |||
| 4357fd022d | |||
| d6416d9c6b | |||
| 7c1125e213 | |||
| b91af2e889 | |||
| 4a97cf230b | |||
| 0c7e3ab365 | |||
| 222113b0d5 | |||
| 895e44c984 | |||
| ef3d03200e | |||
| 6c891ff64a | |||
| d9ba5ab4f1 | |||
| 1816ca5165 | |||
| dff7d65476 | |||
| ce57669263 | |||
| 2444aab204 | |||
| 7b2f2de1f0 | |||
| 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 | |||
| ab711109a7 | |||
| 72623df38f | |||
| 9fd6839878 | |||
| fd403f8520 | |||
| 882752472e | |||
| 87531237c7 | |||
| ee7fc637f1 | |||
| bccfc11687 | |||
| 6fc65e195c | |||
| e9ae850002 |
@@ -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,0 +1,5 @@
|
||||
.ai
|
||||
.junie
|
||||
.git
|
||||
storage
|
||||
tests
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
class ChangeCostUnitDetailsCommand {
|
||||
private ChangeCostUnitDetailsRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitDetailsRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute(): ChangeCostUnitDetailsResponse {
|
||||
$response = new ChangeCostUnitDetailsResponse();
|
||||
|
||||
$this->request->costUnit->distance_allowance = $this->request->distanceAllowance->getAmount();
|
||||
$this->request->costUnit->mail_on_new = $this->request->mailOnNew;
|
||||
$this->request->costUnit->billing_deadline = $this->request->billingDeadline;
|
||||
|
||||
$response->success = $this->request->costUnit->save();
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class ChangeCostUnitDetailsRequest {
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public Amount $distanceAllowance;
|
||||
public bool $mailOnNew;
|
||||
public ?DateTime $billingDeadline;
|
||||
|
||||
public function __construct(CostUnit $costUnit, Amount $distanceAllowance, bool $mailOnNew, ?DateTime $billingDeadline = null) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->distanceAllowance = $distanceAllowance;
|
||||
$this->mailOnNew = $mailOnNew;
|
||||
$this->billingDeadline = $billingDeadline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
class ChangeCostUnitDetailsResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
class ChangeCostUnitStateCommand {
|
||||
private ChangeCostUnitStateRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitStateRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeCostUnitStateResponse {
|
||||
$response = new ChangeCostUnitStateResponse();
|
||||
|
||||
$this->request->costUnit->allow_new = $this->request->allowNew;
|
||||
$this->request->costUnit->archived = $this->request->isArchived;
|
||||
$response->success = $this->request->costUnit->save();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class ChangeCostUnitStateRequest {
|
||||
public bool $allowNew;
|
||||
public bool $isArchived;
|
||||
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(CostUnit $costUnit, bool $allowNew, bool $isArchived) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->allowNew = $allowNew;
|
||||
$this->isArchived = $isArchived;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
class ChangeCostUnitStateResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class ChangeCostUnitTreasurersCommand {
|
||||
private ChangeCostUnitTreasurersRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitTreasurersRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeCostUnitTreasurersResponse {
|
||||
$response = new ChangeCostUnitTreasurersResponse();
|
||||
|
||||
try {
|
||||
$this->request->costUnit->resetTreasurers();
|
||||
|
||||
foreach ($this->request->treasurers as $treasurer) {
|
||||
$this->request->costUnit->treasurers()->attach($treasurer);
|
||||
}
|
||||
|
||||
$this->request->costUnit->save();
|
||||
} catch (\Throwable $th) {
|
||||
$response->success = false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class ChangeCostUnitTreasurersRequest {
|
||||
public array $treasurers;
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(CostUnit $costUnit, array $treasurers) {
|
||||
$this->treasurers = $treasurers;
|
||||
$this->costUnit = $costUnit;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
class ChangeCostUnitTreasurersResponse {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class CreateCostUnitCommand {
|
||||
private CreateCostUnitRequest $request;
|
||||
|
||||
public function __construct(CreateCostUnitRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : CreateCostUnitResponse {
|
||||
$response = new CreateCostUnitResponse();
|
||||
$costUnit = CostUnit::create([
|
||||
'name' => $this->request->name,
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->type,
|
||||
'billing_deadline' => $this->request->billingDeadline,
|
||||
'distance_allowance' => $this->request->distanceAllowance->getAmount(),
|
||||
'mail_on_new' => $this->request->mailOnNew,
|
||||
'allow_new' => true,
|
||||
'archived' => false,
|
||||
]);
|
||||
|
||||
if (null !== $costUnit) {
|
||||
$response->costUnit = $costUnit;
|
||||
$response->success = true;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class CreateCostUnitRequest {
|
||||
public string $name;
|
||||
public string $type;
|
||||
public Amount $distanceAllowance;
|
||||
public bool $mailOnNew;
|
||||
public ?DateTime $billingDeadline;
|
||||
|
||||
public function __construct(string $name, string $type, Amount $distanceAllowance, bool $mailOnNew, ?DateTime $billingDeadline = null) {
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->distanceAllowance = $distanceAllowance;
|
||||
$this->mailOnNew = $mailOnNew;
|
||||
$this->billingDeadline = $billingDeadline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class CreateCostUnitResponse {
|
||||
public bool $success;
|
||||
public ?CostUnit $costUnit;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->costUnit = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitState\ChangeCostUnitStateCommand;
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitState\ChangeCostUnitStateRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ChangeStateController extends CommonController {
|
||||
public function close(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$changeStatRequest = new ChangeCostUnitStateRequest($costUnit, false, false);
|
||||
return $this->changeCostUnitState($changeStatRequest, 'Der CostUnit wurde geschlossen.');
|
||||
}
|
||||
|
||||
public function open(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$changeStatRequest = new ChangeCostUnitStateRequest($costUnit, true, false);
|
||||
return $this->changeCostUnitState($changeStatRequest, 'Der CostUnit wurde geöffnet.');
|
||||
}
|
||||
|
||||
public function archive(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$changeStatRequest = new ChangeCostUnitStateRequest($costUnit, false, true);
|
||||
return $this->changeCostUnitState($changeStatRequest, 'Der CostUnit wurde archiviert.');
|
||||
}
|
||||
|
||||
private function changeCostUnitState(ChangeCostUnitStateRequest $request, string $responseMessage) : JsonResponse {
|
||||
$changeStatCommand = new ChangeCostUnitStateCommand($request);
|
||||
|
||||
if ($changeStatCommand->execute()) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => $responseMessage
|
||||
]);
|
||||
};
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Ein Fehler ist aufgetreten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitCommand;
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Models\CostUnit;
|
||||
use App\Providers\FlashMessageProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreateController extends CommonController{
|
||||
public function showForm() {
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/Create', []);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function createCostUnitRunningJob(Request $request) : JsonResponse {
|
||||
$createCostUnitRequest = new CreateCostUnitRequest(
|
||||
$request->get('cost_unit_name'),
|
||||
CostUnitType::COST_UNIT_TYPE_RUNNING_JOB,
|
||||
Amount::fromString($request->get('distance_allowance')),
|
||||
$request->get('mailOnNew')
|
||||
);
|
||||
|
||||
$createCostUnitCommand = new CreateCostUnitCommand($createCostUnitRequest);
|
||||
$result = $createCostUnitCommand->execute();
|
||||
new FlashMessageProvider('Die laufende Tätigkeit wurde erfolgreich angelegt.', 'success');
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class DistanceAllowanceController extends CommonController {
|
||||
public function __invoke(int $costUnitId) : JsonResponse {
|
||||
$distanceAllowance = 0.00;
|
||||
$costUnit = $this->costUnits->getById($costUnitId, true);
|
||||
if (null !== $costUnit) {
|
||||
$distanceAllowance = $costUnit->distance_allowance;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'distanceAllowance' => $distanceAllowance
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitDetails\ChangeCostUnitDetailsCommand;
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitDetails\ChangeCostUnitDetailsRequest;
|
||||
use App\Resources\CostUnitResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EditController extends CommonController{
|
||||
function __invoke(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kotenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => new CostUnitResource($costUnit)->toArray(request())
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kotenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
$saveParams = $request->get('formData');
|
||||
$distanceAllowance = Amount::fromString($saveParams['distanceAllowance']);
|
||||
$billingDeadline = isset($saveParams['billingDeadline']) ? \DateTime::createFromFormat('Y-m-d', $saveParams['billingDeadline']) : null;
|
||||
|
||||
$request = new ChangeCostUnitDetailsRequest($costUnit, $distanceAllowance, $saveParams['mailOnNew'], $billingDeadline);
|
||||
$command = new ChangeCostUnitDetailsCommand($request);
|
||||
$result = $command->execute();
|
||||
|
||||
if (!$result->success) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Bei der Verarbeitung ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Die Kostenstellendetails wurden erfolgreich gespeichert.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListController extends CommonController {
|
||||
public function __invoke() {
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/List', [
|
||||
'cost_unit_id' => 1
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function listCurrentEvents(Request $request) : JsonResponse {
|
||||
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Aktuelle Veranstaltungen',
|
||||
'cost_units' => $this->costUnits->getCurrentEvents(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function listCurrentRunningJobs(Request $request) : JsonResponse {
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Laufende Tätigkeiten',
|
||||
'cost_units' => $this->costUnits->getRunningJobs(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function listClosedCostUnits(Request $request) : JsonResponse {
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Geschlossene Kostenstellen',
|
||||
'cost_units' => $this->costUnits->getClosedCostUnits(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function listArchivedCostUnits(Request $request) : JsonResponse {
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Archivierte Kostenstellen',
|
||||
'cost_units' => $this->costUnits->getArchivedCostUnits(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class OpenController extends CommonController {
|
||||
public function __invoke(int $costUnitId) {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/Open', [
|
||||
'costUnit' => $costUnit
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function listInvoices(int $costUnitId, string $invoiceStatus) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
$invoices = $this->invoices->getByStatus($costUnit, $invoiceStatus);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => $costUnit,
|
||||
'invoices' => $invoices,
|
||||
'endpoint' => $invoiceStatus,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers\ChangeCostUnitTreasurersCommand;
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers\ChangeCostUnitTreasurersRequest;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\CostUnitResource;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TreasurersEditController extends CommonController {
|
||||
public function __invoke(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kostenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => new CostUnitResource($costUnit)->toArray(request())
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kostenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
$changeTreasurersRequest = new ChangeCostUnitTreasurersRequest($costUnit, $request->get('selectedTreasurers'));
|
||||
$changeTreasurersCommand = new ChangeCostUnitTreasurersCommand($changeTreasurersRequest);
|
||||
if ($changeTreasurersCommand->execute()) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Die Schatzis wurden erfolgreich gespeichert.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Beim Bearbeiten der Kostenstelle ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
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;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('api/v1')
|
||||
->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('cost-unit')->group(function () {
|
||||
|
||||
Route::get('/get-distance-allowance/{costUnitId}', DistanceAllowanceController::class);
|
||||
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::post('/create-running-job', [CreateController::class, 'createCostUnitRunningJob']);
|
||||
|
||||
|
||||
Route::prefix('/{costUnitId}') ->group(function () {
|
||||
Route::get('/invoice-list/{invoiceStatus}', [OpenController::class, 'listInvoices']);
|
||||
|
||||
|
||||
|
||||
|
||||
Route::post('/close', [ChangeStateController::class, 'close']);
|
||||
Route::post('/open', [ChangeStateController::class, 'open']);
|
||||
Route::post('/archive', [ChangeStateController::class, 'archive']);
|
||||
|
||||
Route::get('/details', EditController::class);
|
||||
Route::post('/details', [EditController::class, 'update']);
|
||||
|
||||
Route::get('/treasurers', TreasurersEditController::class);
|
||||
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
||||
|
||||
Route::get('/export-payouts', ExportController::class);
|
||||
});
|
||||
|
||||
|
||||
|
||||
Route::prefix('open')->group(function () {
|
||||
Route::get('/current-events', [ListController::class, 'listCurrentEvents']);
|
||||
Route::get('/current-running-jobs', [ListController::class, 'listCurrentRunningJobs']);
|
||||
Route::get('/closed-cost-units', [ListController::class, 'listClosedCostUnits']);
|
||||
Route::get('/archived-cost-units', [ListController::class, 'listArchivedCostUnits']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\CostUnit\Controllers\OpenController;
|
||||
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||
use App\Domains\UserManagement\Controllers\LoginController;
|
||||
use App\Domains\UserManagement\Controllers\LogOutController;
|
||||
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('cost-unit')->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/create', [CreateController::class, 'showForm']);
|
||||
Route::get('/list', ListController::class);
|
||||
Route::get('/{costUnitId}/', OpenController::class);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
Route::get('/register', [RegistrationController::class, 'loginForm']);
|
||||
Route::get('/register/verifyEmail', [EmailVerificationController::class, 'verifyEmailForm']);
|
||||
|
||||
Route::get('/reset-password', [ResetPasswordController::class, 'resetPasswordForm']);
|
||||
|
||||
route::get('/logout', LogOutController::class);
|
||||
route::post('/login', [LoginController::class, 'doLogin']);
|
||||
route::get('/login', [LoginController::class, 'loginForm']);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup>
|
||||
import { reactive, inject } from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import AmountInput from "../../../Views/Components/AmountInput.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
activeUsers: Object,
|
||||
}
|
||||
)
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
const formData = reactive({
|
||||
cost_unit_name: '',
|
||||
distance_allowance: '0,25',
|
||||
|
||||
emailAddress: '',
|
||||
mailOnNew: true
|
||||
});
|
||||
|
||||
async function save() {
|
||||
const data = await request("/api/v1/cost-unit/create-running-job", {
|
||||
method: "POST",
|
||||
body: {
|
||||
cost_unit_name: formData.cost_unit_name,
|
||||
distance_allowance: formData.distance_allowance,
|
||||
mailOnNew: formData.mailOnNew
|
||||
}
|
||||
});
|
||||
|
||||
window.location.href = '/cost-unit/list';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Laufende Tätigkeit hinzufügen">
|
||||
<form method="POST" action="/api/v1/cost-unit/create-running-job" @submit.prevent="save">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
|
||||
<shadowed-box style="width: 90%; margin: 20px auto; padding: 20px;">
|
||||
<p>
|
||||
Über dieses Formular können laufende Tätigkeiten angelegt werden.<br />
|
||||
Eine Kostenstelle für eine Veranstaltung wird automatisch erstellt, sobald die Veranstaltung angelegt wurde.
|
||||
</p>
|
||||
<table style="margin-top: 40px; width: 100%">
|
||||
<tr>
|
||||
<th class="width-medium pr-20">Name der laufenden Tätigkeit</th>
|
||||
<td><input type="text" v-model="formData.cost_unit_name" class="width-half-full" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="pr-20">Kilometerpauschale</th>
|
||||
<td>
|
||||
<AmountInput v-model="formData.distance_allowance" class="width-small" /> Euro / Kilometer
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<label style="display:flex;align-items:center;cursor:pointer;">
|
||||
<input type="checkbox" v-model="formData.mailOnNew" style="margin-right:8px;cursor:pointer;" />
|
||||
<span>E-Mail-Benachrichtigung bei neuen Abrechnungen</span>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Speichern" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</form>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,83 @@
|
||||
<script setup>
|
||||
import { reactive, inject } from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import AmountInput from "../../../Views/Components/AmountInput.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
|
||||
const props = defineProps({
|
||||
activeUsers: Object,
|
||||
}
|
||||
)
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
const formData = reactive({
|
||||
cost_unit_name: '',
|
||||
distance_allowance: '0,25',
|
||||
|
||||
emailAddress: '',
|
||||
mailOnNew: true
|
||||
});
|
||||
|
||||
async function save() {
|
||||
const data = await request("/wp-json/mareike/costunits/create-new-cost-unit", {
|
||||
method: "POST",
|
||||
body: {
|
||||
mareike_nonce: _mareike_nonce(),
|
||||
cost_unit_name: formData.cost_unit_name,
|
||||
distance_allowance: formData.distance_allowance,
|
||||
email_address: formData.emailAddress,
|
||||
mailOnNew: formData.mailOnNew
|
||||
}
|
||||
});
|
||||
|
||||
toast('Die laufende Tätigkeit wurde erfolgreich angelegt.', { type: 'success' });
|
||||
window.location.href = '/cost-units';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Laufende Tätigkeit hinzufügen">
|
||||
<shadowed-box style="width: 90%; margin: 20px auto; padding: 20px;">
|
||||
<p>
|
||||
Über dieses Formular können laufende Tätigkeiten angelegt werden.<br />
|
||||
Eine Kostenstelle für eine Veranstaltung wird automatisch erstellt, sobald die Veranstaltung angelegt wurde.
|
||||
</p>
|
||||
<table style="margin-top: 40px; width: 100%">
|
||||
<tr>
|
||||
<th class="width-medium pr-20">Name der laufenden Tätigkeit</th>
|
||||
<td><input type="text" v-model="formData.cost_unit_name" class="width-half-full" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="pr-20">Kilometerpauschale</th>
|
||||
<td>
|
||||
<AmountInput v-model="formData.distance_allowance" class="width-small" /> Euro / Kilometer
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<label style="display:flex;align-items:center;cursor:pointer;">
|
||||
<input type="checkbox" v-model="formData.mailOnNew" style="margin-right:8px;cursor:pointer;" />
|
||||
<span>E-Mail-Benachrichtigung bei neuen Abrechnungen</span>
|
||||
</label>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<span v-for="user in props.activeUsers">
|
||||
<input type="checkbox" :id="'user_' + user.id" />
|
||||
<label :for="'user_' + user.id">{{user.fullname}} ({{user.localGroup}})</label>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="button" @click="save" value="Speichern" class="mareike-button button-small button-block" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup>
|
||||
import {reactive, inject, onMounted} from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
import ListCostUnits from "./Partials/ListCostUnits.vue";
|
||||
|
||||
const props = defineProps({
|
||||
message: String,
|
||||
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
cost_unit_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
|
||||
invoice_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
// Prüfen, ob ein ?id= Parameter in der URL übergeben wurde
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.cost_unit_id
|
||||
const initialInvoiceId = props.invoice_id
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Aktuelle Veranstaltungen',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/current-events",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Laufende Tätigkeiten',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/current-running-jobs",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Geschlossene Kostenstellen',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/closed-cost-units",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Archivierte Kostenstellen',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/archived-cost-units",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Kostenstellen">
|
||||
<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" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import {reactive, inject, onMounted} from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import ListInvoices from "./Partials/ListInvoices.vue";
|
||||
|
||||
const props = defineProps({
|
||||
costUnit: Object
|
||||
})
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.costUnit.id
|
||||
const initialInvoiceId = props.invoice_id
|
||||
|
||||
|
||||
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Neue Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/new",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Nichtexportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
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.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.costUnit.id + "/invoice-list/denied",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script setup>
|
||||
|
||||
import {reactive, ref} from "vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}, showCostUnit: Boolean
|
||||
})
|
||||
|
||||
const mail_on_new = ref(Boolean(Number(props.data.mail_on_new)))
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const { request } = useAjax()
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
|
||||
const formData = reactive({
|
||||
billingDeadline: props.data.billingDeadline,
|
||||
mailOnNew: mail_on_new.value,
|
||||
distanceAllowance: props.data.distanceAllowanceSmall,
|
||||
});
|
||||
|
||||
async function updateCostUnit() {
|
||||
const data = await request('/api/v1/cost-unit/' + props.data.id + '/details', {
|
||||
method: "POST",
|
||||
body: {
|
||||
formData
|
||||
}
|
||||
});
|
||||
|
||||
close();
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Modal
|
||||
:show="showCostUnit"
|
||||
title="Details anpassen"
|
||||
@close="emit('close')"
|
||||
>
|
||||
Kilometerpauschale:
|
||||
<amount-input v-model="formData.distanceAllowance" class="width-small" /> Euro / km
|
||||
<br /><br />
|
||||
|
||||
<span v-if="props.data.type !== 'running_job'">
|
||||
Abrechnungsschluss am:
|
||||
<input type="date" style="margin-top: 10px;" id="autoclose_date" v-model="formData.billingDeadline" />
|
||||
<br /><br />
|
||||
</span>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="mail_on_new"
|
||||
v-model="formData.mailOnNew"
|
||||
style="margin-right: 8px; cursor: pointer;"
|
||||
/>
|
||||
<span>E-Mail-Benachrichtigung bei neuen Abrechnungen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<input type="button" value="Speichern" @click="updateCostUnit" />
|
||||
</Modal>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.mareike-save-button {
|
||||
background-color: #2271b1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<script setup>
|
||||
import {createApp, ref} from '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";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
|
||||
deep_jump_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
|
||||
deep_jump_id_sub: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const showInvoiceList = ref(false)
|
||||
const invoices = ref(null)
|
||||
const current_cost_unit = ref(null)
|
||||
const showLoading = ref(false)
|
||||
const show_invoice = ref(false)
|
||||
const invoice = ref(null)
|
||||
|
||||
const show_cost_unit = ref(false)
|
||||
const showTreasurers = ref(false)
|
||||
const costUnit = ref(null)
|
||||
|
||||
const { data, loading, error, request, download } = useAjax()
|
||||
|
||||
async function costUnitDetails(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnit.value = data.costUnit
|
||||
show_cost_unit.value = true
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function editTreasurers(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/treasurers', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnit.value = data.costUnit
|
||||
showTreasurers.value = true
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
function loadInvoices(cost_unit_id) {
|
||||
window.location.href = '/cost-unit/' + cost_unit_id;
|
||||
}
|
||||
|
||||
async function denyNewRequests(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'close');
|
||||
}
|
||||
|
||||
|
||||
async function archiveCostUnit(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'archive');
|
||||
}
|
||||
|
||||
|
||||
async function allowNewRequests(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'open');
|
||||
}
|
||||
|
||||
|
||||
async function changeCostUnitState(costUnitId, endPoint) {
|
||||
showLoading.value = true;
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/' + endPoint, {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
document.getElementById('costUnitBox_' + costUnitId).style.display = 'none';
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 (data.tenant.download_exports) {
|
||||
const response = await fetch(exportUrl, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
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 = "Abrechnungen-Sippenstunden.zip";
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(a);
|
||||
}, 100);
|
||||
} else {
|
||||
const response = await request(exportUrl, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
toast.success(response.message);
|
||||
}
|
||||
showLoading.value = false;
|
||||
|
||||
} catch (err) {
|
||||
showLoading.value = false;
|
||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.data.cost_units && props.data.cost_units.length > 0 && !showInvoiceList">
|
||||
<h2>{{ props.data.cost_unit_title }}</h2>
|
||||
<span v-for="costUnit in props.data.cost_units" class="costunit-list" :id="'costUnitBox_' + costUnit.id">
|
||||
<table style="width: 100%">
|
||||
<thead>
|
||||
<tr><td colspan="5">
|
||||
{{ costUnit.name }}
|
||||
</td></tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<th>Gesamtbeitrag</th>
|
||||
<td>{{ costUnit.totalAmount }}</td>
|
||||
<th>Unbearbeitet</th>
|
||||
<td>{{ costUnit.countNewInvoices }}</td>
|
||||
<td rowspan="4" style="vertical-align: top;">
|
||||
<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" @click="exportPayouts(costUnit.id)" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Spenden</th>
|
||||
<td>{{ costUnit.donatedAmount }}</td>
|
||||
|
||||
<th>Nicht exportiert</th>
|
||||
<td>{{ costUnit.countApprovedInvoices }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
|
||||
<th>Ohne Auszahlung</th>
|
||||
<td colspan="2">{{ costUnit.countDonatedInvoices }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
|
||||
<th>Abgelehnt</th>
|
||||
<td colspan="2">{{ costUnit.countDeniedInvoices }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" style="width: 100%; padding-top: 20px;">
|
||||
<strong @click="costUnitDetails(costUnit.id)" v-if="costUnit.allow_new" class="link">Details anpassen</strong>
|
||||
<strong @click="editTreasurers(costUnit.id)" v-if="!costUnit.archived" class="link">Schatzis zuweisen</strong>
|
||||
<strong @click="denyNewRequests(costUnit.id)" v-if="costUnit.allow_new" class="link" style="color: #ff0000">Neue Abrechnungen verbieten</strong>
|
||||
<strong @click="allowNewRequests(costUnit.id)" v-if="!costUnit.allow_new && !costUnit.archived" class="link" style="color: #529a30">Neue Abrechnungen erlauben</strong>
|
||||
<strong @click="archiveCostUnit(costUnit.id)" v-if="!costUnit.allow_new && !costUnit.archived" class="link" style="color: #ff0000">Veranstaltung archivieren</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
|
||||
<CostUnitDetails :data="costUnit" :showCostUnit="show_cost_unit" v-if="show_cost_unit" @close="show_cost_unit = false" />
|
||||
<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" />
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<strong style="width: 100%; text-align: center; display: block; margin-top: 20px;">
|
||||
Es gibt keine Kostenstellen in dieser Kategorie, für die du verantwortlich bist.
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<LoadingModal :show="showLoading" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.costunit-list {
|
||||
width: 96% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
import Icon from "../../../../Views/Components/Icon.vue";
|
||||
import InvoiceDetails from "../../../Invoice/Views/Partials/invoiceDetails/InvoiceDetails.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import {ref} from "vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: Object
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
const invoice = ref(null)
|
||||
const show_invoice = ref(false)
|
||||
const localData = ref(props.data)
|
||||
console.log(props.data)
|
||||
async function openInvoiceDetails(invoiceId) {
|
||||
const url = '/api/v1/invoice/details/' + invoiceId
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
|
||||
const result = await response.json()
|
||||
invoice.value = result.invoice
|
||||
show_invoice.value = true
|
||||
} catch (err) {
|
||||
console.error('Error fetching invoices:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
const url = "/api/v1/cost-unit/" + props.data.costUnit.id + "/invoice-list/" + props.data.endpoint
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
if (!response.ok) throw new Error('Fehler beim Laden')
|
||||
|
||||
const result = await response.json()
|
||||
localData.value = result
|
||||
} catch (err) {
|
||||
console.error('Error fetching invoices:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function exportPayouts() {
|
||||
toast.info('Der Export wird nun gestartet. Bitte verlasse diese Seite nicht, bis der Export abgeschlossen ist.')
|
||||
|
||||
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||
const data = await response.json();
|
||||
const exportUrl = '/api/v1/cost-unit/' + props.data.costUnit.id + '/export-payouts';
|
||||
|
||||
try {
|
||||
if (data.tenant.download_exports) {
|
||||
const response = await fetch(exportUrl, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
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 = "Abrechnungen-Sippenstunden.zip";
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(a);
|
||||
}, 100);
|
||||
} else {
|
||||
const response = await request(exportUrl, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
toast.success(response.message);
|
||||
}
|
||||
|
||||
reload()
|
||||
|
||||
} catch (err) {
|
||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table v-if="localData.invoices.length > 0" class="invoice-list-table">
|
||||
<tr>
|
||||
<td colspan="6">{{props.data.costUnit.name}}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="invoice in localData.invoices" :id="'invoice_' + invoice.id">
|
||||
<td>{{invoice.invoiceNumber}}</td>
|
||||
<td>{{invoice.invoiceType}}</td>
|
||||
<td>
|
||||
{{invoice.amount}}
|
||||
</td>
|
||||
<td style="width: 150px;">
|
||||
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
||||
<Icon v-if="invoice.externalPayment" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
||||
</td>
|
||||
<td>
|
||||
{{invoice.contactName}}<br />
|
||||
<label v-if="invoice.contactEmail !== '--'">{{invoice.contactEmail}}<br /></label>
|
||||
<label v-if="invoice.contactPhone !== '--'">{{invoice.contactPhone}}<br /></label>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="button" value="Abrechnung Anzeigen" @click="openInvoiceDetails(invoice.id)" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="props.data.endpoint === 'approved'">
|
||||
<td colspan="5"></td>
|
||||
<td>
|
||||
<a style="font-size: 10pt;" class="link" @click="exportPayouts()">Genehmigte Abrechnungen exportieren</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else>Es sind keine Abrechnungen in dieser Kategorie vorhanden.</p>
|
||||
|
||||
<InvoiceDetails :data="invoice" :show-invoice="show_invoice" v-if="show_invoice" @close="show_invoice = false; reload()" />
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<script setup>
|
||||
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const selectedTreasurers = ref([])
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}, showTreasurers: Boolean
|
||||
})
|
||||
|
||||
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);
|
||||
|
||||
selectedTreasurers.value = props.data.treasurers?.map(t => t.id) ?? []
|
||||
});
|
||||
|
||||
|
||||
|
||||
const mail_on_new = ref(Boolean(Number(props.data.mail_on_new)))
|
||||
|
||||
const emit = defineEmits(['closeTreasurers'])
|
||||
|
||||
const { request } = useAjax()
|
||||
function closeTreasurers() {
|
||||
emit('closeTreasurers')
|
||||
}
|
||||
|
||||
|
||||
const formData = reactive({
|
||||
billingDeadline: props.data.billingDeadline,
|
||||
mailOnNew: mail_on_new.value,
|
||||
distanceAllowance: props.data.distanceAllowanceSmall,
|
||||
});
|
||||
|
||||
async function updateCostUnit() {
|
||||
const data = await request('/api/v1/cost-unit/' + props.data.id + '/treasurers', {
|
||||
method: "POST",
|
||||
body: {
|
||||
selectedTreasurers: selectedTreasurers.value,
|
||||
}
|
||||
});
|
||||
|
||||
closeTreasurers();
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Modal
|
||||
:show="showTreasurers"
|
||||
title="Schatzis zuweisen"
|
||||
@close="emit('closeTreasurers')"
|
||||
>
|
||||
<h3>Zuständige Schatzis:</h3>
|
||||
<p v-for="user in commonProps.activeUsers">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="'user_' + user.id"
|
||||
:value="user.id"
|
||||
v-model="selectedTreasurers"
|
||||
/>
|
||||
<label :for="'user_' + user.id">{{user.fullname}}</label>
|
||||
</p>
|
||||
|
||||
<input type="button" value="Speichern" @click="updateCostUnit" />
|
||||
</Modal>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.mareike-save-button {
|
||||
background-color: #2271b1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,8 +5,8 @@ namespace App\Domains\Dashboard\Controllers;
|
||||
use App\Providers\AuthCheckProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DashboardController extends CommonController {
|
||||
public function __invoke(Request $request) {
|
||||
@@ -15,18 +15,36 @@ class DashboardController extends CommonController {
|
||||
}
|
||||
|
||||
return redirect()->intended('/login');
|
||||
|
||||
dd('U');
|
||||
return $this->renderForGuest($request);
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private function renderForGuest(Request $request) {
|
||||
}
|
||||
|
||||
public function getMyInvoices() : JsonResponse {
|
||||
$invoices = $this->invoices->getMyInvoicesWidget();
|
||||
return response()->json(['myInvoices' => $invoices]);
|
||||
}
|
||||
|
||||
public function getOpenCostUnits() : JsonResponse {
|
||||
$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()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||
use App\Domains\Dashboard\Controllers\StorePersonalDataController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::middleware(['auth'])->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);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +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,24 +1,33 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import MyInvoices from "./Partials/Widgets/MyInvoices.vue";
|
||||
import MyParticipations from "./Partials/Widgets/MyParticipations.vue";
|
||||
|
||||
const props = defineProps({
|
||||
navbar: Object,
|
||||
tenant: String,
|
||||
user: Object,
|
||||
currentPath: String,
|
||||
myInvoices: Object,
|
||||
myParticipations: Object,
|
||||
})
|
||||
|
||||
|
||||
function newInvoice() {
|
||||
window.location.href = '/invoice/new';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Dashboard' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||
<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>
|
||||
@@ -36,7 +45,17 @@ const props = defineProps({
|
||||
|
||||
.dashboard-widget-box {
|
||||
flex-grow: 1; display: inline-block;
|
||||
height: 150px;
|
||||
margin: 0 10px;
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive} from "vue";
|
||||
|
||||
const myInvoices = reactive({
|
||||
'myInvoices': '',
|
||||
'approvedInvoices': '',
|
||||
'deniedInvoices': '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/dashboard/my-invoices');
|
||||
const data = await response.json();
|
||||
Object.assign(myInvoices, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>Meine Abrechnungen</h2>
|
||||
<p v-for="invoice in myInvoices.myInvoices" class="widget-content-item">
|
||||
<a :href="'/invoice/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
||||
<label>
|
||||
{{invoice.amount}}
|
||||
</label>
|
||||
</p>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -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 costUnits = reactive({
|
||||
'openCostUnits': '',
|
||||
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/dashboard/open-cost-units');
|
||||
const data = await response.json();
|
||||
Object.assign(costUnits, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="widget-content-item" v-if="costUnits.openCostUnits.length > 0">
|
||||
<tr>
|
||||
<td style="font-weight: bold">Kostenstelle</td>
|
||||
<td style="font-weight: bold">Neu</td>
|
||||
<td style="font-weight: bold">Ang</td>
|
||||
<td style="font-weight: bold">Betrag</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="costUnit in costUnits.openCostUnits">
|
||||
<td><a :href="'/cost-unit/' + costUnit.id" class="link">{{costUnit.name}}</a></td>
|
||||
<td>{{costUnit.new_invoices_count}}</td>
|
||||
<td>{{costUnit.approved_invoices_count}}</td>
|
||||
<td>{{costUnit.totalAmount}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p v-else style="padding: 10px; font-weight: bold">Es existieren im Moment keine Abrechnugnen, um die du dich kümmern musst.</p>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</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>
|
||||
@@ -0,0 +1,392 @@
|
||||
<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 class="personal-data-box">
|
||||
|
||||
<form @submit.prevent="submit">
|
||||
<!-- Sektion: Stammdaten -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Stammdaten</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--readonly">
|
||||
<label>Vorname</label>
|
||||
<div class="pd-readonly">{{ personalData.firstname }}</div>
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--readonly">
|
||||
<label>Nachname</label>
|
||||
<div class="pd-readonly">{{ personalData.lastname }}</div>
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="nickname">Pfadiname</label>
|
||||
<input id="nickname" type="text" v-model="form.nickname" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="birthday">Geburtsdatum</label>
|
||||
<input id="birthday" type="date" v-model="form.birthday" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Kontakt -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Kontakt</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field">
|
||||
<label for="email">E-Mail</label>
|
||||
<input id="email" type="email" v-model="form.email" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="phone">Telefon</label>
|
||||
<input id="phone" type="text" v-model="form.phone" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="address1">Straße / Hausnummer</label>
|
||||
<input id="address1" type="text" v-model="form.address1" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="address2">Adresszusatz</label>
|
||||
<input id="address2" type="text" v-model="form.address2" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--narrow">
|
||||
<label for="postcode">PLZ</label>
|
||||
<input id="postcode" type="text" v-model="form.postcode" maxlength="5" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--wide">
|
||||
<label for="city">Ort</label>
|
||||
<input id="city" type="text" v-model="form.city" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Gesundheit -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Gesundheit</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="medications">Medikamente</label>
|
||||
<input id="medications" type="text" v-model="form.medications" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="allergies">Allergien</label>
|
||||
<input id="allergies" type="text" v-model="form.allergies" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="intolerances">Unverträglichkeiten</label>
|
||||
<input id="intolerances" type="text" v-model="form.intolerances" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="tetanus">Letzte Tetanus-Impfung</label>
|
||||
<input id="tetanus" type="date" v-model="form.tetanusVaccination" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="eating">Ernährungsgewohnheiten</label>
|
||||
<select id="eating" 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>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Erlaubnisse -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Erlaubnisse</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="swimming">Badeerlaubnis</label>
|
||||
<select id="swimming" 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>
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="firstaid">Erste-Hilfe-Erlaubnis</label>
|
||||
<select id="firstaid" 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>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Bankverbindung -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Bankverbindung</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="owner">Kontoinhaber*in</label>
|
||||
<input id="owner" type="text" v-model="form.bankAccountOwner" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="iban">IBAN</label>
|
||||
<IbanInput id="iban" v-model="form.bankAccountIban" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pd-actions">
|
||||
<button type="submit" class="button pd-submit" :disabled="saving">
|
||||
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.personal-data-box {
|
||||
width: 95%;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.pd-fieldset {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
padding: 16px 20px 20px;
|
||||
margin-bottom: 18px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 8px #efefef;
|
||||
}
|
||||
|
||||
.pd-fieldset legend {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
padding: 4px 12px;
|
||||
background-color: #f8fafc;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
color: #1d4899;
|
||||
}
|
||||
|
||||
/* Grid: 2-spaltig auf Desktop */
|
||||
.pd-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px 20px;
|
||||
}
|
||||
|
||||
.pd-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pd-field--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.pd-field--narrow {
|
||||
grid-column: span 1;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.pd-field--wide {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.pd-field label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.pd-field input,
|
||||
.pd-field select {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.pd-field input:focus,
|
||||
.pd-field select:focus {
|
||||
outline: none;
|
||||
border-color: #1d4899;
|
||||
}
|
||||
|
||||
.pd-readonly {
|
||||
padding: 8px 10px;
|
||||
border: 1px dashed #e5e7eb;
|
||||
border-radius: 6px;
|
||||
background-color: #f9fafb;
|
||||
color: #6b7280;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.pd-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.pd-submit {
|
||||
padding: 10px 28px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 1px solid #809dd5;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.pd-submit:hover:not(:disabled) {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pd-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ─── Tablet (640–1023px) ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.personal-data-box {
|
||||
width: 100%;
|
||||
margin: 10px auto;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.pd-fieldset {
|
||||
padding: 12px 14px 16px;
|
||||
}
|
||||
|
||||
.pd-grid {
|
||||
gap: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone (< 640px) ─── */
|
||||
@media (max-width: 639px) {
|
||||
.personal-data-box {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.pd-fieldset {
|
||||
padding: 10px 12px 14px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.pd-fieldset legend {
|
||||
font-size: 0.9rem;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
/* Grid: 1-spaltig auf Smartphone */
|
||||
.pd-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Auch "narrow" und "wide" werden volle Breite */
|
||||
.pd-field--narrow,
|
||||
.pd-field--wide,
|
||||
.pd-field--full {
|
||||
grid-column: 1 / -1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.pd-field label {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.pd-field input,
|
||||
.pd-field select {
|
||||
padding: 10px;
|
||||
font-size: 1rem; /* iOS Zoom-Prevention bei >=16px */
|
||||
}
|
||||
|
||||
.pd-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.pd-submit {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ArchiveEvent;
|
||||
|
||||
class ArchiveEventCommand {
|
||||
public ArchiveEventRequest $request;
|
||||
|
||||
public function __construct(ArchiveEventRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute(): ArchiveEventResponse {
|
||||
$response = new ArchiveEventResponse();
|
||||
|
||||
$this->request->event->archived = true;
|
||||
$this->request->event->save();
|
||||
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ArchiveEvent;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class ArchiveEventRequest {
|
||||
public Event $event;
|
||||
|
||||
public function __construct(Event $event) {
|
||||
$this->event = $event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ArchiveEvent;
|
||||
|
||||
class ArchiveEventResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class CertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
+29
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class ManualCertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+14
@@ -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
|
||||
) {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+9
@@ -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) {}
|
||||
}
|
||||
+10
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UnarchiveEvent;
|
||||
|
||||
class UnarchiveEventCommand {
|
||||
public UnarchiveEventRequest $request;
|
||||
|
||||
public function __construct(UnarchiveEventRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute(): UnarchiveEventResponse {
|
||||
$response = new UnarchiveEventResponse();
|
||||
|
||||
$this->request->event->archived = false;
|
||||
$this->request->event->save();
|
||||
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UnarchiveEvent;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class UnarchiveEventRequest {
|
||||
public Event $event;
|
||||
|
||||
public function __construct(Event $event) {
|
||||
$this->event = $event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UnarchiveEvent;
|
||||
|
||||
class UnarchiveEventResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
) {}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user