Compare commits
48 Commits
825af15962
...
7b2f2de1f0
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 11108bdfcc | |||
| e280fcfba8 | |||
| 3570f442f5 |
81
.ai/conventions.md
Normal file
81
.ai/conventions.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Projektkonventionen
|
||||
|
||||
## Architektur: Actions (Request-Command-Response)
|
||||
|
||||
Jede fachliche Operation wird in eine eigene Action ausgelagert, die aus drei Klassen besteht.
|
||||
Pfad: `app/Domains/{Domain}/Actions/{ActionName}/`
|
||||
|
||||
### Struktur
|
||||
{ActionName}Request.php → Eingabedaten (Konstruktor oder Factory-Methoden) {ActionName}Command.php → Logik, ruft execute(): {ActionName}Response auf {ActionName}Response.php → Rückgabedaten (public Properties)
|
||||
|
||||
|
||||
### Regeln
|
||||
- Der Controller enthält **keine** fachliche Logik – nur Absicherung, Action-Aufruf und HTTP-Response
|
||||
- Commands sind nicht statisch und werden immer instanziiert
|
||||
- Hat ein Request mehrere Varianten, werden **Factory-Methoden** (`forX()`) statt mehrerer Konstruktoren verwendet
|
||||
- Aufrufreihenfolge im Controller: `new Request → new Command(request) → command->execute() → Response verwenden`
|
||||
|
||||
---
|
||||
|
||||
## Controller
|
||||
|
||||
- Alle Controller erben von `App\Scopes\CommonController`
|
||||
- `CommonController` stellt folgende Repositories bereit (keine eigene Instanziierung nötig):
|
||||
- `$this->eventParticipants` → `EventParticipantRepository`
|
||||
- `$this->events` → `EventRepository`
|
||||
- `$this->invoices` → `InvoiceRepository`
|
||||
- `$this->costUnits` → `CostUnitRepository`
|
||||
- `$this->users` → `UserRepository`
|
||||
- `$this->tenant` → aktueller `Tenant`
|
||||
|
||||
- Die Controller besitzen ausschließlich eine __invoke() - Funktion
|
||||
- Für die Speichern-Actions werden separate Controller-Klassen erstellt (z. B. `StoreEventParticipantController`)
|
||||
---
|
||||
|
||||
## Repositories
|
||||
|
||||
- Datenbankzugriffe gehören **immer** ins Repository, nie direkt in Controller oder Actions
|
||||
- Sicherheitschecks (z. B. „gehört diese Teilnahme dem eingeloggten User?") werden als eigene Repository-Methoden gekapselt
|
||||
- Tenant-Filter: `app('tenant')->slug`
|
||||
- Eingeloggter User: `auth()->user()`
|
||||
|
||||
---
|
||||
|
||||
## Models / Ressourcen
|
||||
|
||||
- Models erben von `App\Scopes\InstancedModel` (mit globalem `SiteScope`)
|
||||
- `$model->toResource()->toArray($request)` liefert das aufbereitete Array über die zugehörige Resource-Klasse
|
||||
- Resource-Klassen liegen in `app/Resources/{ModelName}Resource.php`
|
||||
|
||||
---
|
||||
|
||||
## Tenant
|
||||
|
||||
- Der aktuelle Tenant ist per `app('tenant')` verfügbar (gesetzt durch `IdentifyTenant`-Middleware)
|
||||
- Tenant-Slug: `app('tenant')->slug`
|
||||
- Jede tenant-spezifische DB-Abfrage filtert auf `['tenant' => app('tenant')->slug]`
|
||||
|
||||
---
|
||||
|
||||
## Routing
|
||||
|
||||
- API-Routen liegen in `app/Domains/{Domain}/Routes/api.php`
|
||||
- Alle Routen sind in `IdentifyTenant::class`-Middleware gewrappt
|
||||
- Authentifizierte Routen zusätzlich in `['auth']`-Middleware
|
||||
|
||||
---
|
||||
|
||||
## Mails
|
||||
|
||||
- Mails erben von `Illuminate\Mail\Mailable`
|
||||
- Attachments werden über `Attachment::fromData(fn () => $content, $filename)->withMime(...)` angehängt
|
||||
- Werden Daten sowohl in `content()` als auch in `attachments()` benötigt, wird eine **private Hilfsmethode mit Lazy-Caching** verwendet (einmaliges Berechnen, Ergebnis in private Property speichern)
|
||||
- Blade-Templates referenzieren Mail-Attachments per `cid:`-Link: `<a href="cid:{{ $filename }}">...</a>`
|
||||
|
||||
## Actions
|
||||
- Die Actions sind in `app/Domains/{Domain}/Actions/{ActionName}/`-Verzeichnissen organisiert
|
||||
- Jede Action besitzt einen Request, einen Command und einen Response
|
||||
- Der Request besitzt auschließlich einen Konstruktor, der die notwendigen Parameter annimmt
|
||||
- Die Response-Klasse enthält ausschließlich die notwendigen Daten für die Antwort
|
||||
- Die Action-Klasse enthält die Logik für die Verarbeitung der Anfrage und die Generierung der Antwort
|
||||
- Die Logik wird in einer execute() - Funktion innerhalb des Commands impplementiert. Private Funktionen, für ausgelagerte Prozesse sind zulässig, wenn der Code damit lesbarer wird.
|
||||
0
.ai/mcp/mcp.json
Normal file
0
.ai/mcp/mcp.json
Normal file
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
/docker-compose.yaml
|
||||
|
||||
12
.junie/AGENTS.md
Normal file
12
.junie/AGENTS.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Project Guidelines
|
||||
|
||||
This is a placeholder of the project guidelines for Junie.
|
||||
Replace this text with any project-level instructions for Junie, e.g.:
|
||||
|
||||
* What is the project structure
|
||||
* Whether Junie should run tests to check the correctness of the proposed solution
|
||||
* How does Junie run tests (once it requires any non-standard approach)
|
||||
* Whether Junie should build the project before submitting the result
|
||||
* Any code-style related instructions
|
||||
|
||||
As an option you can ask Junie to create these guidelines for you.
|
||||
5
Makefile
5
Makefile
@@ -2,6 +2,11 @@
|
||||
|
||||
FRONTEND_DIR ?= .
|
||||
|
||||
setup:
|
||||
rm -f docker-compose.yaml
|
||||
cp docker-compose.dev docker-compose.yaml
|
||||
docker-compose up -d
|
||||
|
||||
frontend:
|
||||
@cd $(FRONTEND_DIR) && \
|
||||
export QT_QPA_PLATFORM=offscreen && \
|
||||
|
||||
34
app/Casts/AmountCast.php
Normal file
34
app/Casts/AmountCast.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AmountCast implements CastsAttributes
|
||||
{
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ?Amount
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Amount((float) $value, 'Euro');
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): ?float
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof Amount) {
|
||||
return $value->getAmount();
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
47
app/Domains/CostUnit/Controllers/ChangeStateController.php
Normal file
47
app/Domains/CostUnit/Controllers/ChangeStateController.php
Normal file
@@ -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.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
app/Domains/CostUnit/Controllers/CreateController.php
Normal file
36
app/Domains/CostUnit/Controllers/CreateController.php
Normal file
@@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
58
app/Domains/CostUnit/Controllers/EditController.php
Normal file
58
app/Domains/CostUnit/Controllers/EditController.php
Normal file
@@ -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.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Models\Tenant;
|
||||
use App\Providers\FileWriteProvider;
|
||||
use App\Providers\InvoiceCsvFileProvider;
|
||||
use App\Providers\PainFileProvider;
|
||||
use App\Providers\WebDavProvider;
|
||||
use App\Providers\ZipArchiveFileProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ExportController extends CommonController {
|
||||
public function __invoke(int $costUnitId) {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$invoicesForExport = $this->invoices->getByStatus($costUnit, InvoiceStatus::INVOICE_STATUS_APPROVED, false);
|
||||
|
||||
$webdavProvider = new WebDavProvider(WebDavProvider::INVOICE_PREFIX . $this->tenant->url . '/' . $costUnit->name);
|
||||
|
||||
$painFileData = $this->painData($invoicesForExport);
|
||||
$csvData = $this->csvData($invoicesForExport);
|
||||
|
||||
$filePrefix = Tenant::getTempDirectory();
|
||||
|
||||
$painFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '-sepa.xml', $painFileData);
|
||||
$painFileWriteProvider->writeToFile();
|
||||
|
||||
$csvFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '.csv', $csvData);
|
||||
$csvFileWriteProvider->writeToFile();
|
||||
|
||||
if ($this->tenant->upload_exports) {
|
||||
$webdavProvider->uploadFile($painFileWriteProvider->fileName);
|
||||
$webdavProvider->uploadFile($csvFileWriteProvider->fileName);
|
||||
}
|
||||
|
||||
$downloadZipArchiveFiles = [
|
||||
$painFileWriteProvider->fileName,
|
||||
$csvFileWriteProvider->fileName
|
||||
];
|
||||
|
||||
foreach ($invoicesForExport as $invoice) {
|
||||
$changeStatusRequest = new ChangeStatusRequest($invoice, InvoiceStatus::INVOICE_STATUS_EXPORTED);
|
||||
$changeStatusCommand = new ChangeStatusCommand($changeStatusRequest);
|
||||
$changeStatusCommand->execute();
|
||||
|
||||
if ($this->tenant->download_exports) {
|
||||
$createInvoiceReceiptRequest = new CreateInvoiceReceiptRequest($invoice);
|
||||
$createInvoiceReceiptCommand = new CreateInvoiceReceiptCommand($createInvoiceReceiptRequest);
|
||||
$response = $createInvoiceReceiptCommand->execute();
|
||||
|
||||
$downloadZipArchiveFiles[] = $response->fileName;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->tenant->download_exports) {
|
||||
$zipFile = Tenant::getTempDirectory() . 'Abrechnungen-' . $costUnit->name . '.zip';
|
||||
$zipFileProvider = new ZipArchiveFileProvider($zipFile);
|
||||
foreach ($downloadZipArchiveFiles as $file) {
|
||||
$zipFileProvider->addFile($file);
|
||||
}
|
||||
|
||||
$zipFileProvider->create();
|
||||
foreach ($downloadZipArchiveFiles as $file) {
|
||||
Storage::delete($file);
|
||||
}
|
||||
|
||||
Storage::delete($painFileWriteProvider->fileName);
|
||||
Storage::delete($csvFileWriteProvider->fileName);
|
||||
|
||||
return response()->download(
|
||||
storage_path('app/private/' . $zipFile),
|
||||
basename($zipFile),
|
||||
['Content-Type' => 'application/zip']
|
||||
);
|
||||
}
|
||||
|
||||
Storage::delete($painFileWriteProvider->fileName);
|
||||
Storage::delete($csvFileWriteProvider->fileName);
|
||||
return response()->json([
|
||||
'message' => 'Die Abrechnungen wurden exportiert.' . PHP_EOL .'Die Belege werden asynchron auf dem Webdav-Server hinterlegt.' . PHP_EOL . PHP_EOL . 'Sollten diese in 15 Minuten nicht vollständig sein, kontaktiere den Adminbistrator.'
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
private function painData(array $invoices) : string {
|
||||
$invoicesForPainFile = [];
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($invoice->contact_bank_owner !== null && $invoice->contact_bank_iban !== '' && !$invoice->donation) {
|
||||
$invoicesForPainFile[] = $invoice;
|
||||
}
|
||||
}
|
||||
|
||||
$painFileProvider = new PainFileProvider($this->tenant->account_iban, $this->tenant->account_name, $this->tenant->account_bic, $invoicesForPainFile);
|
||||
return $painFileProvider->createPainFileContent();
|
||||
}
|
||||
|
||||
public function csvData(array $invoices) : string {
|
||||
$csvDateProvider = new InvoiceCsvFileProvider($invoices);
|
||||
return $csvDateProvider->createCsvFileContent();
|
||||
}
|
||||
}
|
||||
|
||||
48
app/Domains/CostUnit/Controllers/ListController.php
Normal file
48
app/Domains/CostUnit/Controllers/ListController.php
Normal file
@@ -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(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
31
app/Domains/CostUnit/Controllers/OpenController.php
Normal file
31
app/Domains/CostUnit/Controllers/OpenController.php
Normal file
@@ -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.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
55
app/Domains/CostUnit/Routes/api.php
Normal file
55
app/Domains/CostUnit/Routes/api.php
Normal file
@@ -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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
app/Domains/CostUnit/Routes/web.php
Normal file
39
app/Domains/CostUnit/Routes/web.php
Normal file
@@ -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']);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
78
app/Domains/CostUnit/Views/Create.vue
Normal file
78
app/Domains/CostUnit/Views/Create.vue
Normal file
@@ -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>
|
||||
83
app/Domains/CostUnit/Views/Edit.vue
Normal file
83
app/Domains/CostUnit/Views/Edit.vue
Normal file
@@ -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>
|
||||
82
app/Domains/CostUnit/Views/List.vue
Normal file
82
app/Domains/CostUnit/Views/List.vue
Normal file
@@ -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>
|
||||
67
app/Domains/CostUnit/Views/Open.vue
Normal file
67
app/Domains/CostUnit/Views/Open.vue
Normal file
@@ -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>
|
||||
90
app/Domains/CostUnit/Views/Partials/CostUnitDetails.vue
Normal file
90
app/Domains/CostUnit/Views/Partials/CostUnitDetails.vue
Normal file
@@ -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>
|
||||
230
app/Domains/CostUnit/Views/Partials/ListCostUnits.vue
Normal file
230
app/Domains/CostUnit/Views/Partials/ListCostUnits.vue
Normal file
@@ -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>
|
||||
80
app/Domains/CostUnit/Views/Partials/ListInvoices.vue
Normal file
80
app/Domains/CostUnit/Views/Partials/ListInvoices.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<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";
|
||||
|
||||
const props = defineProps({
|
||||
data: Object
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
const invoice = ref(null)
|
||||
const show_invoice = ref(false)
|
||||
const localData = ref(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)
|
||||
}
|
||||
}
|
||||
|
||||
</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.alreadyPaid" 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>
|
||||
</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>
|
||||
96
app/Domains/CostUnit/Views/Partials/Treasurers.vue
Normal file
96
app/Domains/CostUnit/Views/Partials/Treasurers.vue
Normal file
@@ -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;
|
||||
}
|
||||
50
app/Domains/Dashboard/Controllers/DashboardController.php
Normal file
50
app/Domains/Dashboard/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
|
||||
class DashboardController extends CommonController {
|
||||
public function __invoke(Request $request) {
|
||||
if ($this->checkAuth()) {
|
||||
return $this->renderForLoggedInUser($request);
|
||||
}
|
||||
|
||||
return redirect()->intended('/login');
|
||||
}
|
||||
|
||||
private function renderForLoggedInUser(Request $request) {
|
||||
$authCheckProvider = new AuthCheckProvider;
|
||||
$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()]);
|
||||
}
|
||||
}
|
||||
14
app/Domains/Dashboard/Controllers/MessagesController.php
Normal file
14
app/Domains/Dashboard/Controllers/MessagesController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
|
||||
class MessagesController extends CommonController {
|
||||
public function __invoke() {
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/Messages', []);
|
||||
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
45
app/Domains/Dashboard/Controllers/PersonalDataController.php
Normal file
45
app/Domains/Dashboard/Controllers/PersonalDataController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
|
||||
class PersonalDataController extends CommonController
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
if (!$this->checkAuth()) {
|
||||
return redirect()->intended('/login');
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$data = $this->users->getPersonalData($user);
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/PersonalData', [
|
||||
'personalData' => [
|
||||
'firstname' => $data['firstname'],
|
||||
'lastname' => $data['lastname'],
|
||||
'birthday' => $data['birthday'],
|
||||
'nickname' => $data['nickname'],
|
||||
'email' => $data['email'],
|
||||
'phone' => $data['phone'],
|
||||
'address1' => $data['address_1'],
|
||||
'address2' => $data['address_2'],
|
||||
'postcode' => $data['postcode'],
|
||||
'city' => $data['city'],
|
||||
'medications' => $data['medications'],
|
||||
'allergies' => $data['allergies'],
|
||||
'intolerances' => $data['intolerances'],
|
||||
'eatingHabits' => $data['eating_habits'],
|
||||
'swimmingPermission' => $data['swimming_permission'],
|
||||
'firstAidPermission' => $data['first_aid_permission'],
|
||||
'bankAccountOwner' => $data['bank_account_owner'],
|
||||
'bankAccountIban' => $data['bank_account_iban'],
|
||||
'tetanusVaccination' => $data['tetanus_vaccination'],
|
||||
],
|
||||
]);
|
||||
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Domains\Dashboard\Actions\UpdatePersonalData\UpdatePersonalDataCommand;
|
||||
use App\Domains\Dashboard\Actions\UpdatePersonalData\UpdatePersonalDataRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StorePersonalDataController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$actionRequest = new UpdatePersonalDataRequest(
|
||||
user: $user,
|
||||
nickname: $request->input('nickname'),
|
||||
email: $request->input('email'),
|
||||
phone: $request->input('phone'),
|
||||
address1: $request->input('address1'),
|
||||
address2: $request->input('address2'),
|
||||
postcode: $request->input('postcode'),
|
||||
city: $request->input('city'),
|
||||
medications: $request->input('medications'),
|
||||
allergies: $request->input('allergies'),
|
||||
intolerances: $request->input('intolerances'),
|
||||
eatingHabits: $request->input('eatingHabits'),
|
||||
swimmingPermission: $request->input('swimmingPermission'),
|
||||
firstAidPermission: $request->input('firstAidPermission'),
|
||||
bankAccountOwner: $request->input('bankAccountOwner'),
|
||||
bankAccountIban: $request->input('bankAccountIban'),
|
||||
birthday: $request->input('birthday'),
|
||||
tetanusVaccination: $request->input('tetanusVaccination'),
|
||||
);
|
||||
|
||||
$command = new UpdatePersonalDataCommand($actionRequest, $this->users);
|
||||
$command->execute();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Deine Daten wurden erfolgreich gespeichert.']);
|
||||
}
|
||||
}
|
||||
21
app/Domains/Dashboard/Routes/api.php
Normal file
21
app/Domains/Dashboard/Routes/api.php
Normal file
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
16
app/Domains/Dashboard/Routes/web.php
Normal file
16
app/Domains/Dashboard/Routes/web.php
Normal file
@@ -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);
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
61
app/Domains/Dashboard/Views/Dashboard.vue
Normal file
61
app/Domains/Dashboard/Views/Dashboard.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<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({
|
||||
myInvoices: Object,
|
||||
myParticipations: Object,
|
||||
})
|
||||
|
||||
|
||||
function newInvoice() {
|
||||
window.location.href = '/invoice/new';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Dashboard'>
|
||||
<diV class="dashboard-widget-container">
|
||||
<shadowed-box class="dashboard-widget-box" style="width: 60%;">
|
||||
<MyParticipations />
|
||||
</shadowed-box>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.dashboard-widget-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.dashboard-widget-box {
|
||||
flex-grow: 1; display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.dashboard-widget-box h2 {
|
||||
border-color: #c0c0c0;
|
||||
border-left-width: 40px;
|
||||
border-left-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
padding: 5px 10px;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
</style>
|
||||
25
app/Domains/Dashboard/Views/Messages.vue
Normal file
25
app/Domains/Dashboard/Views/Messages.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Meine Nachrichten'>
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
Diese Funktion steht aktuell nicht zur Verfügung.<br />
|
||||
Bitte versuche es später noch einmal.
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
32
app/Domains/Dashboard/Views/Partials/Widgets/MyInvoices.vue
Normal file
32
app/Domains/Dashboard/Views/Partials/Widgets/MyInvoices.vue
Normal file
@@ -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>
|
||||
187
app/Domains/Dashboard/Views/PersonalData.vue
Normal file
187
app/Domains/Dashboard/Views/PersonalData.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { request } from '../../../../resources/js/components/HttpClient.js'
|
||||
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import IbanInput from "../../../Views/Components/IbanInput.vue";
|
||||
|
||||
const props = defineProps({
|
||||
personalData: Object,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
nickname: props.personalData.nickname ?? '',
|
||||
email: props.personalData.email ?? '',
|
||||
phone: props.personalData.phone ?? '',
|
||||
address1: props.personalData.address1 ?? '',
|
||||
address2: props.personalData.address2 ?? '',
|
||||
postcode: props.personalData.postcode ?? '',
|
||||
city: props.personalData.city ?? '',
|
||||
birthday: props.personalData.birthday ?? '',
|
||||
tetanusVaccination: props.personalData.tetanusVaccination ?? '',
|
||||
medications: props.personalData.medications ?? '',
|
||||
allergies: props.personalData.allergies ?? '',
|
||||
intolerances: props.personalData.intolerances ?? '',
|
||||
eatingHabits: props.personalData.eatingHabits ?? '',
|
||||
swimmingPermission: props.personalData.swimmingPermission ?? '',
|
||||
firstAidPermission: props.personalData.firstAidPermission ?? '',
|
||||
bankAccountOwner: props.personalData.bankAccountOwner ?? '',
|
||||
bankAccountIban: props.personalData.bankAccountIban ?? '',
|
||||
})
|
||||
|
||||
const saving = ref(false)
|
||||
const successMessage = ref('')
|
||||
const errorMessage = ref('')
|
||||
|
||||
const submit = async () => {
|
||||
saving.value = true
|
||||
successMessage.value = ''
|
||||
errorMessage.value = ''
|
||||
|
||||
const result = await request('/api/v1/dashboard/personal-data', {
|
||||
method: 'POST',
|
||||
body: { ...form },
|
||||
})
|
||||
|
||||
saving.value = false
|
||||
|
||||
if (result?.success) {
|
||||
toast.success(result.message)
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Persönliche Daten'>
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
|
||||
<div class="max-w-2xl mx-auto p-6">
|
||||
<form @submit.prevent="submit">
|
||||
<table class="form-table" style="width: 90%; margin: 10px;">
|
||||
|
||||
<!-- Nicht veränderbare Felder -->
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Vorname:</td>
|
||||
<td><span class="text-gray-700">{{ personalData.firstname }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Nachname:</td>
|
||||
<td><span class="text-gray-700">{{ personalData.lastname }}</span></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Veränderbare Felder -->
|
||||
<tr>
|
||||
<td>Pfadiname:</td>
|
||||
<td><input type="text" v-model="form.nickname" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E-Mail:</td>
|
||||
<td><input type="email" v-model="form.email" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telefon:</td>
|
||||
<td><input type="text" v-model="form.phone" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Straße / Hausnummer:</td>
|
||||
<td><input type="text" v-model="form.address1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Adresszusatz:</td>
|
||||
<td><input type="text" v-model="form.address2" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PLZ:</td>
|
||||
<td><input type="text" v-model="form.postcode" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ort:</td>
|
||||
<td><input type="text" v-model="form.city" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Geburtsdatum:</td>
|
||||
<td><input type="date" v-model="form.birthday" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Medikamente:</td>
|
||||
<td><input type="text" v-model="form.medications" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allergien:</td>
|
||||
<td><input type="text" v-model="form.allergies" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unverträglichkeiten:</td>
|
||||
<td><input type="text" v-model="form.intolerances" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Letzte Tetanus-Impfung:</td>
|
||||
<td><input type="date" v-model="form.tetanusVaccination" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ernährungsgewohnheiten:</td>
|
||||
<td>
|
||||
<select v-model="form.eatingHabits">
|
||||
<option value="EATING_HABIT_VEGAN">Vegan</option>
|
||||
<option value="EATING_HABIT_VEGETARIAN">Vegetarisch</option>
|
||||
<option value="EATING_HABIT_OMNIVOR">Omnivor</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Badeerlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="form.swimmingPermission">
|
||||
<option value="SWIMMING_PERMISSION_ALLOWED">Erteilt, kann schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_LIMITED">Erteilt, kann nicht schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_DENIED">Nicht erteilt</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Erste-Hilfe-Erlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="form.firstAidPermission">
|
||||
<option value="FIRST_AID_PERMISSION_ALLOWED">Erweiterte Erste Hilfe erlaubt</option>
|
||||
<option value="FIRST_AID_PERMISSION_DENIED">Erweiterte Erste Hilfe verweigert</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kontoinhaber*in:</td>
|
||||
<td><input type="text" v-model="form.bankAccountOwner" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IBAN:</td>
|
||||
<td><IbanInput v-model="form.bankAccountIban" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" class="btn-row" style="padding-top: 20px;">
|
||||
<button type="submit" class="button" :disabled="saving">
|
||||
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CertificateOfConductionCheckCommand {
|
||||
function __construct(public CertificateOfConductionCheckRequest $request)
|
||||
{}
|
||||
|
||||
public function execute() : CertificateOfConductionCheckResponse {
|
||||
$response = new CertificateOfConductionCheckResponse();
|
||||
|
||||
$localGroup = str_replace('Stamm ', '', $this->request->participant->localGroup()->first()->name);
|
||||
|
||||
$apiResponse = Http::acceptJson()
|
||||
->asJson()
|
||||
->withoutVerifying()
|
||||
->post(env('COC_CHECK_URL'), [
|
||||
'firstName' => trim($this->request->participant->firstname),
|
||||
'lastName' => $this->request->participant->lastname,
|
||||
'nickname' => $this->request->participant->nickname,
|
||||
'address' => trim($this->request->participant->address_1 . ' ' . $this->request->participant->address_2),
|
||||
'zip' => $this->request->participant->zip,
|
||||
'city' => $this->request->participant->city,
|
||||
'birthday' => $this->request->participant->birthday->format('Y-m-d'),
|
||||
'email' => $this->request->participant->email_1,
|
||||
'localGroup' => $localGroup,
|
||||
'checkForDate' => $this->request->participant->departure_date->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
if (! $apiResponse->successful()) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$responseParsed = $apiResponse->json();
|
||||
|
||||
if (($responseParsed['status'] ?? null) !== '200/ok') {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->status = match ($responseParsed['efzStatus']) {
|
||||
'NOT_REQUIRED' => EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||
'CHECKED_VALID' => EfzStatus::EFZ_STATUS_CHECKED_VALID,
|
||||
'CHECKED_INVALID' => EfzStatus::EFZ_STATUS_CHECKED_INVALID,
|
||||
default => EfzStatus::EFZ_STATUS_NOT_CHECKED
|
||||
};
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class CertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
|
||||
class CertificateOfConductionCheckResponse {
|
||||
public string $status;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->status = EfzStatus::EFZ_STATUS_NOT_CHECKED;
|
||||
}
|
||||
}
|
||||
76
app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
Normal file
76
app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Models\Event;
|
||||
use App\Models\Tenant;
|
||||
use App\RelationModels\EventEatingHabits;
|
||||
use App\RelationModels\EventLocalGroups;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateEventCommand {
|
||||
private CreateEventRequest $request;
|
||||
public function __construct(CreateEventRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
public function execute(): CreateEventResponse {
|
||||
$response = new CreateEventResponse();
|
||||
|
||||
|
||||
|
||||
$prefix = $this->request->begin->format('Y-m_');
|
||||
if (!str_starts_with($this->request->name, $prefix)) {
|
||||
$this->request->name = $prefix . $this->request->name;
|
||||
}
|
||||
|
||||
|
||||
$event = Event::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'name' => $this->request->name,
|
||||
'identifier' => Str::random(10),
|
||||
'location' => $this->request->location,
|
||||
'postal_code' => $this->request->postalCode,
|
||||
'email' => $this->request->email,
|
||||
'start_date' => $this->request->begin,
|
||||
'end_date' => $this->request->end,
|
||||
'early_bird_end' => $this->request->earlyBirdEnd,
|
||||
'registration_final_end' => $this->request->registrationFinalEnd,
|
||||
'early_bird_end_amount_increase' => $this->request->earlyBirdEndAmountIncrease,
|
||||
'account_owner' => $this->request->accountOwner,
|
||||
'account_iban' => $this->request->accountIban,
|
||||
'participation_fee_type' => $this->request->participationFeeType->slug,
|
||||
'pay_per_day' => $this->request->payPerDay,
|
||||
'pay_direct' => $this->request->payDirect,
|
||||
'total_max_amount' => 0,
|
||||
'support_per_person' => 0,
|
||||
'support_flat' => 0,
|
||||
]);
|
||||
|
||||
if ($event !== null) {
|
||||
EventEatingHabits::create([
|
||||
'event_id' => $event->id,
|
||||
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGAN)->first()->id,
|
||||
]);
|
||||
|
||||
EventEatingHabits::create([
|
||||
'event_id' => $event->id,
|
||||
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGETARIAN)->first()->id,
|
||||
]);
|
||||
|
||||
if (app('tenant')->slug === 'lv') {
|
||||
foreach(Tenant::where(['is_active_local_group' => true])->get() as $tenant) {
|
||||
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => $tenant->id]);
|
||||
}
|
||||
} else {
|
||||
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => app('tenant')->id]);
|
||||
}
|
||||
|
||||
|
||||
$response->success = true;
|
||||
$response->event = $event;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
40
app/Domains/Event/Actions/CreateEvent/CreateEventRequest.php
Normal file
40
app/Domains/Event/Actions/CreateEvent/CreateEventRequest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use DateTime;
|
||||
|
||||
class CreateEventRequest {
|
||||
public string $name;
|
||||
public string $location;
|
||||
public string $postalCode;
|
||||
public string $email;
|
||||
public DateTime $begin;
|
||||
public DateTime $end;
|
||||
public DateTime $earlyBirdEnd;
|
||||
public DateTime $registrationFinalEnd;
|
||||
public int $earlyBirdEndAmountIncrease;
|
||||
public ParticipationFeeType $participationFeeType;
|
||||
public string $accountOwner;
|
||||
public string $accountIban;
|
||||
public bool $payPerDay;
|
||||
public bool $payDirect;
|
||||
|
||||
public function __construct(string $name, string $location, string $postalCode, string $email, DateTime $begin, DateTime $end, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $earlyBirdEndAmountIncrease, ParticipationFeeType $participationFeeType, string $accountOwner, string $accountIban, bool $payPerDay, bool $payDirect) {
|
||||
$this->name = $name;
|
||||
$this->location = $location;
|
||||
$this->postalCode = $postalCode;
|
||||
$this->email = $email;
|
||||
$this->begin = $begin;
|
||||
$this->end = $end;
|
||||
$this->earlyBirdEnd = $earlyBirdEnd;
|
||||
$this->registrationFinalEnd = $registrationFinalEnd;
|
||||
$this->earlyBirdEndAmountIncrease = $earlyBirdEndAmountIncrease;
|
||||
$this->participationFeeType = $participationFeeType;
|
||||
$this->accountOwner = $accountOwner;
|
||||
$this->accountIban = $accountIban;
|
||||
$this->payPerDay = $payPerDay;
|
||||
$this->payDirect = $payDirect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class CreateEventResponse {
|
||||
public bool $success;
|
||||
public ?Event $event;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->event = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcal;
|
||||
|
||||
class GenerateIcalCommand
|
||||
{
|
||||
public function __construct(public GenerateIcalRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): GenerateIcalResponse
|
||||
{
|
||||
$participant = $this->request->participant;
|
||||
$event = $participant->event;
|
||||
|
||||
$uid = $participant->identifier . '@' . app('tenant')->slug;
|
||||
$dtStart = $event->start_date->format('Ymd');
|
||||
$dtEnd = $event->end_date->copy()->addDay()->format('Ymd');
|
||||
$now = now()->format('Ymd\THis\Z');
|
||||
$summary = $this->escapeIcal($event->name);
|
||||
$location = $this->escapeIcal(trim($event->postal_code . ' ' . $event->location));
|
||||
$description = $this->escapeIcal('Teilnahme als: ' . $participant->getOfficialName());
|
||||
|
||||
$icalContent = implode("\r\n", [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//' . app('tenant')->name . '//Veranstaltungskalender//DE',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'BEGIN:VEVENT',
|
||||
'UID:' . $uid,
|
||||
'DTSTAMP:' . $now,
|
||||
'DTSTART;VALUE=DATE:' . $dtStart,
|
||||
'DTEND;VALUE=DATE:' . $dtEnd,
|
||||
'SUMMARY:' . $summary,
|
||||
'LOCATION:' . $location,
|
||||
'DESCRIPTION:' . $description,
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
]) . "\r\n";
|
||||
|
||||
$filename = 'veranstaltung-' . $participant->event()->first()->name . '.ics';
|
||||
|
||||
return new GenerateIcalResponse($icalContent, $filename);
|
||||
}
|
||||
|
||||
private function escapeIcal(string $value): string
|
||||
{
|
||||
return str_replace(
|
||||
['\\', ';', ',', "\n"],
|
||||
['\\\\', '\\;', '\\,', '\\n'],
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcal;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class GenerateIcalRequest
|
||||
{
|
||||
public function __construct(public EventParticipant $participant)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcal;
|
||||
|
||||
class GenerateIcalResponse
|
||||
{
|
||||
public function __construct(
|
||||
public string $icalContent,
|
||||
public string $filename,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcalForDeadline;
|
||||
|
||||
class GenerateIcalForDeadlineCommand {
|
||||
public function __construct(public GenerateIcalForDeadlineRequest $request)
|
||||
{
|
||||
}
|
||||
public function execute(): GenerateIcalForDeadlineResponse
|
||||
{
|
||||
$event = $this->request->event;
|
||||
|
||||
$deadlineDate = $event->registration_final_end->copy()->subDays(2);
|
||||
$dtDate = $deadlineDate->format('Ymd');
|
||||
$now = now()->format('Ymd\THis\Z');
|
||||
$summary = $this->escapeIcal('Zahlungsfrist: ' . $event->name);
|
||||
$description = $this->escapeIcal(
|
||||
'Bitte überweise den Teilnahmebeitrag für "' . $event->name . '" bis zu diesem Datum.'
|
||||
);
|
||||
|
||||
$icalContent = implode("\r\n", [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//' . app('tenant')->name . '//Veranstaltungskalender//DE',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'BEGIN:VEVENT',
|
||||
'UID:payment-deadline-' . $event->identifier . '@' . app('tenant')->slug,
|
||||
'DTSTAMP:' . $now,
|
||||
'DTSTART;VALUE=DATE:' . $dtDate,
|
||||
'DTEND;VALUE=DATE:' . $dtDate,
|
||||
'SUMMARY:' . $summary,
|
||||
'DESCRIPTION:' . $description,
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
]) . "\r\n";
|
||||
|
||||
$filename = 'zahlungsziel-' . $event->name . '.ics';
|
||||
|
||||
return new GenerateIcalForDeadlineResponse($icalContent, $filename);
|
||||
}
|
||||
|
||||
private function escapeIcal(string $value): string
|
||||
{
|
||||
return str_replace(
|
||||
['\\', ';', ',', "\n"],
|
||||
['\\\\', '\\;', '\\,', '\\n'],
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcalForDeadline;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class GenerateIcalForDeadlineRequest {
|
||||
public function __construct(public Event $event) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\GenerateIcalForDeadline;
|
||||
|
||||
class GenerateIcalForDeadlineResponse
|
||||
{
|
||||
public function __construct(
|
||||
public string $icalContent,
|
||||
public string $filename,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ManualCertificateOfConductionCheckCommand {
|
||||
function __construct(public ManualCertificateOfConductionCheckRequest $request)
|
||||
{}
|
||||
|
||||
public function execute() : ManualCertificateOfConductionCheckResponse {
|
||||
$response = new ManualCertificateOfConductionCheckResponse();
|
||||
|
||||
|
||||
$this->request->participant->efz_status = EfzStatus::EFZ_STATUS_CHECKED_VALID;
|
||||
$this->request->participant->save();
|
||||
$response->success = true;
|
||||
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantCocCompleteMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class ManualCertificateOfConductionCheckRequest {
|
||||
function __construct(public EventParticipant $participant)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ManualCertificateOfConductionCheck;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
|
||||
class ManualCertificateOfConductionCheckResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ParticipantPayment;
|
||||
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentOverpaidMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentPaidMail;
|
||||
use App\Providers\MissingPaymentProvider;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ParticipantPaymentCommand {
|
||||
public function __construct(public ParticipantPaymentRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() : ParticipantPaymentResponse {
|
||||
$response = new ParticipantPaymentResponse();
|
||||
|
||||
$this->request->participant->amount_paid = $this->request->amountPaid;
|
||||
$this->request->participant->save();
|
||||
|
||||
$response->amountPaid = $this->request->participant->amount_paid;
|
||||
$response->amountExpected = $this->request->participant->amount;
|
||||
|
||||
$amountToPay = MissingPaymentProvider::calculateMissingPayment(
|
||||
amountPaid: $this->request->participant->amount_paid,
|
||||
amountToPay: $this->request->participant->amount,
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case $amountToPay->getAmount() > 0:
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
case $amountToPay->getAmount() < 0:
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$response->participant = $this->request->participant;
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ParticipantPayment;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class ParticipantPaymentRequest {
|
||||
public EventParticipant $participant;
|
||||
public Amount $amountPaid;
|
||||
|
||||
public function __construct(EventParticipant $participant, Amount $amountPaid) {
|
||||
$this->participant = $participant;
|
||||
$this->amountPaid = $amountPaid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\ParticipantPayment;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class ParticipantPaymentResponse {
|
||||
public bool $success;
|
||||
public ?Amount $amountPaid;
|
||||
public ?Amount $amountExpected;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->amountPaid = null;
|
||||
$this->amountExpected = null;
|
||||
$this->success = false;
|
||||
$this->participant = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendMissingPaymentMailsCommand {
|
||||
function __construct(public SendMissingPaymentMailsRequest $request) {}
|
||||
|
||||
public function execute() : SendMissingPaymentMailsResponse {
|
||||
$response = new SendMissingPaymentMailsResponse();
|
||||
|
||||
foreach ($this->request->eventParticipants->getParticipantsWithMissingPayments($this->request->event, $this->request->httpRequest) as $participant) {
|
||||
$participantResource = $participant->toResource()->toArray($this->request->httpRequest);
|
||||
if (!$participantResource['needs_payment']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null && $participant->email_2 !== $participant->email_1) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
|
||||
$response->remindedParticipants++;
|
||||
}
|
||||
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Repositories\EventParticipantRepository;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SendMissingPaymentMailsRequest {
|
||||
function __construct(
|
||||
public Event $event,
|
||||
public EventParticipantRepository $eventParticipants,
|
||||
public Request $httpRequest
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||
|
||||
class SendMissingPaymentMailsResponse {
|
||||
function __construct(
|
||||
public bool $success = false,
|
||||
public int $remindedParticipants = 0
|
||||
) {}
|
||||
}
|
||||
18
app/Domains/Event/Actions/SetCostUnit/SetCostUnitCommand.php
Normal file
18
app/Domains/Event/Actions/SetCostUnit/SetCostUnitCommand.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||
|
||||
class SetCostUnitCommand {
|
||||
private SetCostUnitRequest $request;
|
||||
|
||||
public function __construct(SetCostUnitRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : SetCostUnitResponse {
|
||||
$response = new SetCostUnitResponse();
|
||||
$this->request->event->cost_unit_id = $this->request->costUnit->id;
|
||||
$response->success = $this->request->event->save();
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
16
app/Domains/Event/Actions/SetCostUnit/SetCostUnitRequest.php
Normal file
16
app/Domains/Event/Actions/SetCostUnit/SetCostUnitRequest.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
use App\Models\Event;
|
||||
|
||||
class SetCostUnitRequest {
|
||||
public Event $event;
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(Event $event, CostUnit $costUnit) {
|
||||
$this->event = $event;
|
||||
$this->costUnit = $costUnit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||
|
||||
class SetCostUnitResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||
|
||||
use App\RelationModels\EventParticipationFee;
|
||||
|
||||
class SetParticipationFeesCommand {
|
||||
private SetParticipationFeesRequest $request;
|
||||
public function __construct(SetParticipationFeesRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : SetParticipationFeesResponse {
|
||||
$response = new SetParticipationFeesResponse();
|
||||
$this->request->event->sibling_reduction = $this->request->siblingReduction;
|
||||
$this->request->event->save();
|
||||
|
||||
$this->cleanBefore();
|
||||
|
||||
$this->request->event->participationFee1()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeFirst['type'],
|
||||
'name' => $this->request->participationFeeFirst['name'],
|
||||
'description' => $this->request->participationFeeFirst['description'],
|
||||
'amount_standard' => $this->request->participationFeeFirst['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeFirst['amount_reduced'] ? $this->request->participationFeeFirst['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeFirst['amount_solidarity'] ? $this->request->participationFeeFirst['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
|
||||
if ($this->request->participationFeeSecond !== null) {
|
||||
$this->request->event->participationFee2()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeSecond['type'],
|
||||
'name' => $this->request->participationFeeSecond['name'],
|
||||
'description' => $this->request->participationFeeSecond['description'],
|
||||
'amount_standard' => $this->request->participationFeeSecond['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeSecond['amount_reduced'] ? $this->request->participationFeeSecond['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeSecond['amount_solidarity'] ? $this->request->participationFeeSecond['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
if ($this->request->participationFeeThird !== null) {
|
||||
$this->request->event->participationFee3()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeThird['type'],
|
||||
'name' => $this->request->participationFeeThird['name'],
|
||||
'description' => $this->request->participationFeeThird['description'],
|
||||
'amount_standard' => $this->request->participationFeeThird['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeThird['amount_reduced'] ? $this->request->participationFeeThird['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeThird['amount_solidarity'] ? $this->request->participationFeeThird['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
if ($this->request->participationFeeFourth !== null) {
|
||||
$this->request->event->participationFee4()->associate(EventParticipationFee::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->participationFeeFourth['type'],
|
||||
'name' => $this->request->participationFeeFourth['name'],
|
||||
'description' => $this->request->participationFeeFourth['description'],
|
||||
'amount_standard' => $this->request->participationFeeFourth['amount_standard']->getAmount(),
|
||||
'amount_reduced' => null !== $this->request->participationFeeFourth['amount_reduced'] ? $this->request->participationFeeFourth['amount_reduced']->getAmount() : null,
|
||||
'amount_solidarity' => null !== $this->request->participationFeeFourth['amount_solidarity'] ? $this->request->participationFeeFourth['amount_solidarity']->getAmount() : null,
|
||||
]))->save();
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function cleanBefore() {
|
||||
if ($this->request->event->participationFee1()->first() !== null) {
|
||||
$this->request->event->participationFee1()->first()->delete();
|
||||
}
|
||||
|
||||
if ($this->request->event->participationFee2()->first() !== null) {
|
||||
$this->request->event->participationFee2()->first()->delete();
|
||||
}
|
||||
|
||||
if ($this->request->event->participationFee3()->first() !== null) {
|
||||
$this->request->event->participationFee3()->first()->delete();
|
||||
}
|
||||
|
||||
if ($this->request->event->participationFee4()->first() !== null) {
|
||||
$this->request->event->participationFee4()->first()->delete();
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\RelationModels\EventParticipationFee;
|
||||
|
||||
class SetParticipationFeesRequest {
|
||||
public Event $event;
|
||||
public array $participationFeeFirst;
|
||||
public ?array $participationFeeSecond;
|
||||
public ?array $participationFeeThird;
|
||||
public ?array $participationFeeFourth;
|
||||
|
||||
public bool $siblingReduction;
|
||||
|
||||
|
||||
public function __construct(Event $event, array $participationFeeFirst, bool $siblingReduction) {
|
||||
$this->event = $event;
|
||||
$this->participationFeeFirst = $participationFeeFirst;
|
||||
$this->participationFeeSecond = null;
|
||||
$this->participationFeeThird = null;
|
||||
$this->participationFeeFourth = null;
|
||||
$this->siblingReduction = $siblingReduction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||
|
||||
class SetParticipationFeesResponse {
|
||||
public bool $success;
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
||||
use App\Mail\ParticipantParticipationMails\ParticipantSignOffMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SetParticipationStateCommand {
|
||||
function __construct(private SetParticipationStateSignoffRequest|SetParticipationStateReSignonRequest $request) {}
|
||||
|
||||
public function execute() : SetParticipationStateResponse {
|
||||
$response = new SetParticipationStateResponse();
|
||||
|
||||
|
||||
switch (true) {
|
||||
case $this->request instanceof SetParticipationStateSignoffRequest:
|
||||
$this->request->participant->unregistered_at = $this->request->date;
|
||||
$this->request->participant->save();
|
||||
$response->success = true;
|
||||
Mail::to($this->request->participant->email_1)->send(new ParticipantSignOffMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new ParticipantSignOffMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
|
||||
break;
|
||||
case $this->request instanceof SetParticipationStateReSignonRequest:
|
||||
$this->request->participant->unregistered_at = null;
|
||||
$this->request->participant->save();
|
||||
|
||||
Mail::to($this->request->participant->email_1)->send(new EventSignUpSuccessfullMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
|
||||
if ($this->request->participant->email_2 !== null) {
|
||||
Mail::to($this->request->participant->email_2)->send(new EventSignUpSuccessfullMail(
|
||||
participant: $this->request->participant,
|
||||
));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SetParticipationStateReSignonRequest {
|
||||
function __construct(public EventParticipant $participant) {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
class SetParticipationStateResponse {
|
||||
function __construct(public bool $success = false) {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SetParticipationState;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SetParticipationStateSignoffRequest {
|
||||
function __construct(public EventParticipant $participant, public \DateTime $date) {
|
||||
}
|
||||
}
|
||||
71
app/Domains/Event/Actions/SignUp/SignUpCommand.php
Normal file
71
app/Domains/Event/Actions/SignUp/SignUpCommand.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\ValueObjects\Age;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SignUpCommand {
|
||||
public function __construct(public SignUpRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() : SignUpResponse {
|
||||
$response = new SignUpResponse();
|
||||
|
||||
$eatingHabit = match ($this->request->eating_habit) {
|
||||
'vegan' => EatingHabit::EATING_HABIT_VEGAN,
|
||||
'vegetarian' => EatingHabit::EATING_HABIT_VEGETARIAN,
|
||||
default => EatingHabit::EATING_HABIT_OMNIVOR,
|
||||
};
|
||||
|
||||
$participantAge = new Age($this->request->birthday);
|
||||
$response->participant = $this->request->event->participants()->create(
|
||||
[
|
||||
'tenant' => $this->request->event->tenant,
|
||||
'user_id' => $this->request->user_id,
|
||||
'identifier' => Str::random(10),
|
||||
'firstname' => $this->request->firstname,
|
||||
'lastname' => $this->request->lastname,
|
||||
'nickname' => $this->request->nickname,
|
||||
'participation_type' => $this->request->participationType,
|
||||
'local_group' => $this->request->localGroup->slug,
|
||||
'birthday' => $this->request->birthday,
|
||||
'address_1' => $this->request->address_1,
|
||||
'address_2' => $this->request->address_2,
|
||||
'postcode' => $this->request->postcode,
|
||||
'city' => $this->request->city,
|
||||
'email_1' => $this->request->email_1,
|
||||
'email_2' => $this->request->email_2,
|
||||
'phone_1' => $this->request->phone_1,
|
||||
'phone_2' => $this->request->phone_2,
|
||||
'contact_person' => $this->request->contact_person,
|
||||
'allergies' => $this->request->allergies,
|
||||
'intolerances' => $this->request->intolerances,
|
||||
'medications' => $this->request->medications,
|
||||
'tetanus_vaccination' => $this->request->tetanus_vaccination,
|
||||
'eating_habit' => $eatingHabit,
|
||||
'swimming_permission' => $participantAge->isfullAged() ? 'SWIMMING_PERMISSION_ALLOWED' : $this->request->swimming_permission,
|
||||
'first_aid_permission' => $participantAge->isfullAged() ? 'FIRST_AID_PERMISSION_ALLOWED' : $this->request->first_aid_permission,
|
||||
'foto_socialmedia' => $this->request->foto_socialmedia,
|
||||
'foto_print' => $this->request->foto_print,
|
||||
'foto_webseite' => $this->request->foto_webseite,
|
||||
'foto_partner' => $this->request->foto_partner,
|
||||
'foto_intern' => $this->request->foto_intern,
|
||||
'arrival_date' => $this->request->arrival,
|
||||
'departure_date' => $this->request->departure,
|
||||
'arrival_eating' => $this->request->arrival_eating,
|
||||
'departure_eating' => $this->request->departure_eating,
|
||||
'notes' => $this->request->notes,
|
||||
'amount' => $this->request->amount,
|
||||
'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname,
|
||||
'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||
]
|
||||
);
|
||||
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
50
app/Domains/Event/Actions/SignUp/SignUpRequest.php
Normal file
50
app/Domains/Event/Actions/SignUp/SignUpRequest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\Event;
|
||||
use App\Models\Tenant;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class SignUpRequest {
|
||||
function __construct(
|
||||
public Event $event,
|
||||
public ?int $user_id,
|
||||
public string $firstname,
|
||||
public string $lastname,
|
||||
public ?string $nickname,
|
||||
public string $participationType,
|
||||
public Tenant $localGroup,
|
||||
public DateTime $birthday,
|
||||
public string $address_1,
|
||||
public string $address_2,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
public string $email_1,
|
||||
public ?string $phone_1,
|
||||
public ?string $email_2,
|
||||
public ?string $phone_2,
|
||||
public ?string $contact_person,
|
||||
public ?string $allergies,
|
||||
public ?string $intolerances,
|
||||
public ?string $medications,
|
||||
public ?DateTime $tetanus_vaccination,
|
||||
public string $eating_habit,
|
||||
public ?string $swimming_permission,
|
||||
public ?string $first_aid_permission,
|
||||
public bool $foto_socialmedia,
|
||||
public bool $foto_print,
|
||||
public bool $foto_webseite,
|
||||
public bool $foto_partner,
|
||||
public bool $foto_intern,
|
||||
public DateTime $arrival,
|
||||
public DateTime $departure,
|
||||
public int $arrival_eating,
|
||||
public int $departure_eating,
|
||||
public ?string $notes,
|
||||
public Amount $amount
|
||||
) {
|
||||
}
|
||||
}
|
||||
16
app/Domains/Event/Actions/SignUp/SignUpResponse.php
Normal file
16
app/Domains/Event/Actions/SignUp/SignUpResponse.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class SignUpResponse {
|
||||
public bool $success;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->participant = null;
|
||||
}
|
||||
|
||||
}
|
||||
43
app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php
Normal file
43
app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
class UpdateEventCommand {
|
||||
public UpdateEventRequest $request;
|
||||
public function __construct(UpdateEventRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UpdateEventResponse {
|
||||
$response = new UpdateEventResponse();
|
||||
|
||||
$this->request->event->name = $this->request->eventName;
|
||||
$this->request->event->location = $this->request->eventLocation;
|
||||
$this->request->event->postal_code = $this->request->postalCode;
|
||||
$this->request->event->email = $this->request->email;
|
||||
$this->request->event->early_bird_end = $this->request->earlyBirdEnd;
|
||||
$this->request->event->registration_final_end = $this->request->registrationFinalEnd;
|
||||
$this->request->event->alcoholics_age = $this->request->alcoholicsAge;
|
||||
$this->request->event->support_per_person = $this->request->supportPerPerson;
|
||||
$this->request->event->support_flat = $this->request->flatSupport;
|
||||
$this->request->event->send_weekly_report = $this->request->sendWeeklyReports;
|
||||
$this->request->event->registration_allowed = $this->request->registrationAllowed;
|
||||
$this->request->event->save();
|
||||
|
||||
$this->request->event->resetAllowedEatingHabits();
|
||||
$this->request->event->resetContributingLocalGroups();
|
||||
|
||||
foreach($this->request->eatingHabits as $eatingHabit) {
|
||||
$this->request->event->eatingHabits()->attach($eatingHabit);
|
||||
}
|
||||
|
||||
foreach($this->request->contributingLocalGroups as $contributingLocalGroup) {
|
||||
$this->request->event->localGroups()->attach($contributingLocalGroup);
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php
Normal file
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class UpdateEventRequest {
|
||||
public Event $event;
|
||||
public string $eventName;
|
||||
public string $eventLocation;
|
||||
public string $postalCode;
|
||||
public string $email;
|
||||
public DateTime $earlyBirdEnd;
|
||||
public DateTime $registrationFinalEnd;
|
||||
public int $alcoholicsAge;
|
||||
public bool $sendWeeklyReports;
|
||||
public bool $registrationAllowed;
|
||||
public Amount $flatSupport;
|
||||
public Amount $supportPerPerson;
|
||||
public array $contributingLocalGroups;
|
||||
public array $eatingHabits;
|
||||
|
||||
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits) {
|
||||
$this->event = $event;
|
||||
$this->eventName = $eventName;
|
||||
$this->eventLocation = $eventLocation;
|
||||
$this->postalCode = $postalCode;
|
||||
$this->email = $email;
|
||||
$this->earlyBirdEnd = $earlyBirdEnd;
|
||||
$this->registrationFinalEnd = $registrationFinalEnd;
|
||||
$this->alcoholicsAge = $alcoholicsAge;
|
||||
$this->sendWeeklyReports = $sendWeeklyReports;
|
||||
$this->registrationAllowed = $registrationAllowed;
|
||||
$this->flatSupport = $flatSupport;
|
||||
$this->supportPerPerson = $supportPerPerson;
|
||||
$this->contributingLocalGroups = $contributingLocalGroups;
|
||||
$this->eatingHabits = $eatingHabits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
class UpdateEventResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||
|
||||
class UpdateManagersCommand {
|
||||
private UpdateManagersRequest $request;
|
||||
|
||||
public function __construct(UpdateManagersRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UpdateManagersResponse {
|
||||
$response = new UpdateManagersResponse();
|
||||
$this->request->event->resetMangers();
|
||||
|
||||
foreach ($this->request->managers as $manager) {
|
||||
$this->request->event->eventManagers()->attach($manager);
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
$response->success = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||
|
||||
use App\Models\Event;
|
||||
|
||||
class UpdateManagersRequest {
|
||||
public Event $event;
|
||||
public array $managers;
|
||||
|
||||
public function __construct(Event $event, array $managers) {
|
||||
$this->managers = $managers;
|
||||
$this->event = $event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||
|
||||
class UpdateManagersResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateParticipant;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocCompleteMail;
|
||||
use App\Mail\ParticipantCocMails\ParticipantCocInvalidMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentOverpaidMail;
|
||||
use App\Mail\ParticipantPaymentMails\ParticipantPaymentPaidMail;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Providers\MissingPaymentProvider;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class UpdateParticipantCommand {
|
||||
private UpdateParticipantResponse $response;
|
||||
function __construct(public UpdateParticipantRequest $request) {
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$this->response = new UpdateParticipantResponse();
|
||||
|
||||
$p = clone($this->request->participant);
|
||||
|
||||
$p->firstname = $this->request->firstname;
|
||||
$p->lastname = $this->request->lastname;
|
||||
$p->nickname = $this->request->nickname;
|
||||
$p->address_1 = $this->request->address_1;
|
||||
$p->address_2 = $this->request->address_2;
|
||||
$p->postcode = $this->request->postcode;
|
||||
$p->city = $this->request->city;
|
||||
$p->local_group = $this->request->localgroup;
|
||||
$p->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday);
|
||||
$p->email_1 = $this->request->email_1;
|
||||
$p->phone_1 = $this->request->phone_1;
|
||||
$p->contact_person = $this->request->contact_person;
|
||||
$p->email_2 = $this->request->email_2;
|
||||
$p->phone_2 = $this->request->phone_2;
|
||||
$p->arrival_date = DateTime::createFromFormat('Y-m-d', $this->request->arrival);
|
||||
$p->departure_date = DateTime::createFromFormat('Y-m-d', $this->request->departure);
|
||||
$p->participation_type = $this->request->participationType;
|
||||
$p->eating_habit = $this->request->eatingHabit;
|
||||
$p->allergies = $this->request->allergies;
|
||||
$p->intolerances = $this->request->intolerances;
|
||||
$p->medications = $this->request->medications;
|
||||
$p->first_aid_permission = $this->request->extendedFirstAid;
|
||||
$p->swimming_permission = $this->request->swimmingPermission;
|
||||
$p->tetanus_vaccination = $this->request->tetanusVaccination !== null
|
||||
? DateTime::createFromFormat('Y-m-d', $this->request->tetanusVaccination)
|
||||
: null;
|
||||
$p->notes = $this->request->notes;
|
||||
$p->amount_paid = Amount::fromString($this->request->amountPaid);
|
||||
$p->amount = Amount::fromString($this->request->amountExpected);
|
||||
$p->efz_status = $this->request->cocStatus;
|
||||
|
||||
if (
|
||||
MissingPaymentProvider::calculateMissingPayment(amountPaid: $p->amount_paid, amountToPay: $p->amount)->getAmount()
|
||||
!==
|
||||
MissingPaymentProvider::calculateMissingPayment(amountPaid: $this->request->participant->amount_paid, amountToPay: $this->request->participant->amount)->getAmount()
|
||||
) {
|
||||
$this->handleAmountChanges($p);
|
||||
}
|
||||
|
||||
if (
|
||||
$p->efz_status !== $this->request->participant->efz_status
|
||||
) {
|
||||
$this->handleCocStatusChange($p);
|
||||
}
|
||||
|
||||
|
||||
$p->save();
|
||||
$this->response->success = true;
|
||||
$this->response->participant = $p;
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
private function handleAmountChanges(EventParticipant $participant) {
|
||||
$this->response->amountPaid = $participant->amount_paid;
|
||||
$this->response->amountExpected = $participant->amount;
|
||||
|
||||
$amountToPay = MissingPaymentProvider::calculateMissingPayment(
|
||||
amountPaid: $participant->amount_paid,
|
||||
amountToPay: $participant->amount,
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case $amountToPay->getAmount() > 0:
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
case $amountToPay->getAmount() < 0:
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentOverpaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Mail::to($participant->email_1)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
|
||||
if ($participant->email_2 !== null) {
|
||||
Mail::to($participant->email_2)->send(new ParticipantPaymentPaidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleCocStatusChange(EventParticipant $participant) {
|
||||
$this->response->cocStatus = $participant->efzStatus()->first();
|
||||
|
||||
switch ($participant->efzStatus()->first()->slug) {
|
||||
case EfzStatus::EFZ_STATUS_CHECKED_VALID:
|
||||
case EfzStatus::EFZ_STATUS_NOT_REQUIRED:
|
||||
Mail::to($participant->email_1)->send(new ParticipantCocCompleteMail(
|
||||
participant: $participant,
|
||||
));
|
||||
break;
|
||||
|
||||
case EfzStatus::EFZ_STATUS_CHECKED_INVALID:
|
||||
Mail::to($participant->email_1)->send(new ParticipantCocInvalidMail(
|
||||
participant: $participant,
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateParticipant;
|
||||
|
||||
use App\Models\EventParticipant;
|
||||
|
||||
class UpdateParticipantRequest {
|
||||
function __construct(
|
||||
public EventParticipant $participant,
|
||||
public string $firstname,
|
||||
public string $lastname,
|
||||
public ?string $nickname,
|
||||
public string $address_1,
|
||||
public ?string $address_2,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
public string $localgroup,
|
||||
public string $birthday,
|
||||
public string $email_1,
|
||||
public string $phone_1,
|
||||
public string $contact_person,
|
||||
public ?string $email_2,
|
||||
public ?string $phone_2,
|
||||
public string $arrival,
|
||||
public string $departure,
|
||||
public string $participationType,
|
||||
public string $eatingHabit,
|
||||
public ?string $allergies,
|
||||
public ?string $intolerances,
|
||||
public ?string $medications,
|
||||
public string $extendedFirstAid,
|
||||
public string $swimmingPermission,
|
||||
public ?string $tetanusVaccination,
|
||||
public ?string $notes,
|
||||
public string $amountPaid,
|
||||
public string $amountExpected,
|
||||
public string $cocStatus,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateParticipant;
|
||||
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class UpdateParticipantResponse {
|
||||
public bool $success;
|
||||
public ?EfzStatus $cocStatus;
|
||||
public ?Amount $amountPaid;
|
||||
public ?Amount $amountExpected;
|
||||
public ?EventParticipant $participant;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->cocStatus =null;
|
||||
$this->amountPaid = null;
|
||||
$this->amountExpected = null;
|
||||
$this->participant = null;
|
||||
}
|
||||
}
|
||||
22
app/Domains/Event/Controllers/AvailableEventsController.php
Normal file
22
app/Domains/Event/Controllers/AvailableEventsController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class AvailableEventsController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request) : Response {
|
||||
$events = [];
|
||||
foreach ($this->events->getAvailable(false) as $event) {
|
||||
$events[] = $event->toResource()->toArray($request);
|
||||
};
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Event/ListAvailable', ['events' => $events]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
100
app/Domains/Event/Controllers/CreateController.php
Normal file
100
app/Domains/Event/Controllers/CreateController.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitCommand;
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||
use App\Domains\Event\Actions\CreateEvent\CreateEventCommand;
|
||||
use App\Domains\Event\Actions\CreateEvent\CreateEventRequest;
|
||||
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitCommand;
|
||||
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\EventResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreateController extends CommonController {
|
||||
public function __invoke() {
|
||||
return new InertiaProvider('Event/Create', [
|
||||
'emailAddress' => auth()->user()->email,
|
||||
'eventAccount' => $this->tenant->account_name,
|
||||
'eventIban' => $this->tenant->account_iban,
|
||||
'eventPayPerDay' => $this->tenant->slug === 'lv' ? true : false,
|
||||
'participationFeeType' => $this->tenant->slug === 'lv' ?
|
||||
ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED :
|
||||
ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY,
|
||||
])->render();
|
||||
}
|
||||
|
||||
public function doCreate(Request $request) : JsonResponse {
|
||||
|
||||
$eventBegin = DateTime::createFromFormat('Y-m-d', $request->input('eventBegin'));
|
||||
$eventEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventEnd'));
|
||||
$eventEarlyBirdEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventEarlyBirdEnd'));
|
||||
$registrationFinalEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventRegistrationFinalEnd'));
|
||||
$participationFeeType = ParticipationFeeType::where('slug', $request->input('eventParticipationFeeType'))->first();
|
||||
$payPerDay = $request->input('eventPayPerDay');
|
||||
$payDirect = $request->input('eventPayDirectly');
|
||||
|
||||
$billingDeadline = $eventEnd->modify('+1 month');
|
||||
|
||||
$createRequest = new CreateEventRequest(
|
||||
$request->input('eventName'),
|
||||
$request->input('eventLocation'),
|
||||
$request->input('eventPostalCode'),
|
||||
$request->input('eventEmail'),
|
||||
$eventBegin,
|
||||
$eventEnd,
|
||||
$eventEarlyBirdEnd,
|
||||
$registrationFinalEnd,
|
||||
$request->input('eventEarlyBirdEndAmountIncrease'),
|
||||
$participationFeeType,
|
||||
$request->input('eventAccount'),
|
||||
$request->input('eventIban'),
|
||||
$payPerDay,
|
||||
$payDirect
|
||||
);
|
||||
|
||||
$wasSuccessful = false;
|
||||
|
||||
$createCommand = new CreateEventCommand($createRequest);
|
||||
$result = $createCommand->execute();
|
||||
if ($result->success) {
|
||||
$createCostUnitRequest = new CreateCostUnitRequest(
|
||||
$result->event->name,
|
||||
CostUnitType::COST_UNIT_TYPE_EVENT,
|
||||
Amount::fromString('0,25'),
|
||||
true,
|
||||
$billingDeadline
|
||||
);
|
||||
|
||||
$createCostUnitCommand = new CreateCostUnitCommand($createCostUnitRequest);
|
||||
$costUnitResponse = $createCostUnitCommand->execute();
|
||||
|
||||
if ($costUnitResponse->success) {
|
||||
$costUnitUpdateRequest = new SetCostUnitRequest($result->event, $costUnitResponse->costUnit);
|
||||
$costUnitUpdateCommand = new SetCostUnitCommand($costUnitUpdateRequest);
|
||||
$costUnitSetResponse = $costUnitUpdateCommand->execute();
|
||||
$wasSuccessful = $costUnitSetResponse->success;
|
||||
}
|
||||
}
|
||||
|
||||
if ($wasSuccessful) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'event' => new EventResource($costUnitUpdateRequest->event)->toArray($request)
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Veranstaltung konnte nicht angelegt werden.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
198
app/Domains/Event/Controllers/DetailsController.php
Normal file
198
app/Domains/Event/Controllers/DetailsController.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers;
|
||||
|
||||
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesCommand;
|
||||
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesRequest;
|
||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventCommand;
|
||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventRequest;
|
||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
|
||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\EventParticipant;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Providers\PdfGenerateAndDownloadProvider;
|
||||
use App\Resources\EventResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use function Symfony\Component\String\b;
|
||||
|
||||
class DetailsController extends CommonController {
|
||||
public function __invoke(string $eventId) {
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
||||
}
|
||||
|
||||
public function summary(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
return response()->json(['event' => $event->toResource()->toArray($request)]);
|
||||
}
|
||||
|
||||
public function updateCommonSettings(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
|
||||
$earlyBirdEnd = \DateTime::createFromFormat('Y-m-d', $request->input('earlyBirdEnd'));
|
||||
$registrationFinalEnd = \DateTime::createFromFormat('Y-m-d', $request->input('registrationFinalEnd'));
|
||||
$flatSupport = Amount::fromString($request->input('flatSupport'));
|
||||
$supportPerPerson = Amount::fromString($request->input('supportPerson'));
|
||||
|
||||
$contributinLocalGroups = $request->input('contributingLocalGroups');
|
||||
$eatingHabits = $request->input('eatingHabits');
|
||||
|
||||
$eventUpdateRequest = new UpdateEventRequest(
|
||||
$event,
|
||||
$request->input('eventName'),
|
||||
$request->input('eventLocation'),
|
||||
$request->input('postalCode'),
|
||||
$request->input('email'),
|
||||
$earlyBirdEnd,
|
||||
$registrationFinalEnd,
|
||||
$request->input('alcoholicsAge'),
|
||||
$request->input('sendWeeklyReports'),
|
||||
$request->input('registrationAllowed'),
|
||||
$flatSupport,
|
||||
$supportPerPerson,
|
||||
$contributinLocalGroups,
|
||||
$eatingHabits
|
||||
);
|
||||
|
||||
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
||||
$response = $eventUpdateCommand->execute();
|
||||
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
public function updateEventManagers(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
|
||||
$updateEventManagersRequest = new UpdateManagersRequest($event, $request->input('selectedManagers'));
|
||||
$updateEventManagersCommand = new UpdateManagersCommand($updateEventManagersRequest);
|
||||
$response = $updateEventManagersCommand->execute();
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
public function updateParticipationFees(int $eventId, Request $request) : JsonResponse {
|
||||
$event = $this->events->getById($eventId);
|
||||
|
||||
$participationFeeFirst = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||
'name' => 'Teilnehmer',
|
||||
'description' => $request->input('pft_1_description'),
|
||||
'amount_standard' => Amount::fromString($request->input('pft_1_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_1_amount_reduced') ? Amount::fromString($request->input('pft_1_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_1_amount_solidarity') ? Amount::fromString($request->input('pft_1_amount_solidarity')) : null
|
||||
];
|
||||
|
||||
$siblingReduction = $request->input('sibling_reduction') ?? false;
|
||||
$participationFeeRequest = new SetParticipationFeesRequest($event, $participationFeeFirst, $siblingReduction);
|
||||
|
||||
if ($request->input('pft_2_active')) {
|
||||
$participationFeeRequest->participationFeeSecond = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_TEAM,
|
||||
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Kernteam' : 'Solidaritätsbeitrag',
|
||||
'description' => $request->input('pft_2_description'),
|
||||
'amount_standard' => Amount::fromString($request->input('pft_2_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_2_amount_reduced') ? Amount::fromString($request->input('pft_2_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_2_amount_solidarity') ? Amount::fromString($request->input('pft_2_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
if ($request->input('pft_3_active')) {
|
||||
$participationFeeRequest->participationFeeThird = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_VOLUNTEER,
|
||||
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Unterstützende' : 'Reduzierter Beitrag',
|
||||
'description' => $event->participation_fee_type !== ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY ? $request->input('pft_3_description') : 'Nach Verfügbarkeit',
|
||||
'amount_standard' => Amount::fromString($request->input('pft_3_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_3_amount_reduced') ? Amount::fromString($request->input('pft_3_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_3_amount_solidarity') ? Amount::fromString($request->input('pft_3_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
if ($request->input('pft_4_active') && $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED) {
|
||||
$participationFeeRequest->participationFeeFourth = [
|
||||
'type' => ParticipationType::PARTICIPATION_TYPE_OTHER,
|
||||
'name' => 'Sonstige',
|
||||
'description' => $request->input('pft_4_description'),
|
||||
'amount_standard' => Amount::fromString($request->input('pft_4_amount_standard')),
|
||||
'amount_reduced' => null !== $request->input('pft_4_amount_reduced') ? Amount::fromString($request->input('pft_4_amount_reduced')) : null,
|
||||
'amount_solidarity' => null !== $request->input('pft_4_amount_solidarity') ? Amount::fromString($request->input('pft_4_amount_solidarity')) : null
|
||||
];
|
||||
}
|
||||
|
||||
$participationFeeCommand = new SetParticipationFeesCommand($participationFeeRequest);
|
||||
$response = $participationFeeCommand->execute();
|
||||
|
||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||
}
|
||||
|
||||
public function downloadPdfList(string $eventId, string $listType, Request $request): Response
|
||||
{
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
|
||||
$participants = $this->eventParticipants->getForList($event, $request);
|
||||
$kitchenOverview = $this->eventParticipants->getKitchenOverview($event);
|
||||
$html = view('pdfs.' . $listType, [
|
||||
'event' => $event->name,
|
||||
'eventStart' => $event->start_date,
|
||||
'eventEnd' => $event->end_date,
|
||||
'rows' => $participants,
|
||||
'kitchenRequirements' => $kitchenOverview,
|
||||
'participantsForKitchenList' => $this->eventParticipants->getParticipantsWithIntolerances($event, $request),
|
||||
])->render();
|
||||
|
||||
$pdf = PdfGenerateAndDownloadProvider::fromHtml(
|
||||
$html,
|
||||
'landscape'
|
||||
);
|
||||
|
||||
return response($pdf, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="' . $listType .'.pdf"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCsvList(string $eventId, string $listType, Request $request): Response
|
||||
{
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
|
||||
$participants = $this->eventParticipants->getForList($event, $request);
|
||||
$kitchenOverview = $this->eventParticipants->getKitchenOverview($event);
|
||||
|
||||
$csv = view('csvs.' . $listType, [
|
||||
'event' => $event->name,
|
||||
'rows' => $participants,
|
||||
])->render();
|
||||
|
||||
return response($csv, 200, [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
'Content-Disposition' => 'attachment; filename="' . $listType . '.csv"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function listParticipants(string $eventId, string $listType, Request $request) : JsonResponse {
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
switch ($listType) {
|
||||
case 'by-local-group':
|
||||
$participants = $this->eventParticipants->groupByLocalGroup($event, $request);
|
||||
break;
|
||||
case 'by-participation-group':
|
||||
$participants = $this->eventParticipants->groupByParticipationType($event, $request);
|
||||
break;
|
||||
case 'signed-off':
|
||||
$participants = $this->eventParticipants->getSignedOffParticipants($event, $request);
|
||||
break;
|
||||
default:
|
||||
$participants = ['Alle Teilnehmenden' => $this->eventParticipants->getForList($event, $request)];
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'participants' => $participants,
|
||||
'listType' => $listType,
|
||||
'event' => $event->toResource()->toArray($request)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Event\Controllers\MailCompose;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ByGroupController extends CommonController
|
||||
{
|
||||
public function __invoke(string $eventIdentifier, string $groupType, Request $request) {
|
||||
$event = $this->events->getByIdentifier($eventIdentifier, true);
|
||||
$recipients = [];
|
||||
switch ($groupType) {
|
||||
case 'by-local-group':
|
||||
$participants = $this->eventParticipants->groupByLocalGroup($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
case 'by-participation-group':
|
||||
$participants = $this->eventParticipants->groupByParticipationType($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
case 'signed-off':
|
||||
$participants = $this->eventParticipants->getSignedOffParticipants($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
default:
|
||||
$participants = $this->eventParticipants->getForList($event, $request);
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants);
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['recipients' => $recipients]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user