Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e09987f5a8 | |||
| 5c514e9ff5 | |||
| bc60461dac | |||
| 6848fbd95f | |||
| 012ebb6538 | |||
| c1ef1d71ad | |||
| aebb2f9aaa | |||
| cfc7c7eee2 | |||
| 1b9384dad1 | |||
| fed54514c8 | |||
| 12f05ceb09 | |||
| 5f56ef94a6 | |||
| 79bb186234 | |||
| a012c16425 | |||
| a8205a4f96 | |||
| dbcebbb2c4 | |||
| 63c7b8dfb1 | |||
| e9aa66a860 | |||
| 51c4055c47 | |||
| 2348663fd8 | |||
| a83cec94ab | |||
| 710e27c344 | |||
| 83bbd6f7d3 | |||
| 28ffbdb696 | |||
| 575fb27018 | |||
| fe3429cd4e | |||
| 551b592b3b | |||
| 6ed0a5b93a | |||
| 97fd7cd0da | |||
| f5d7b21671 | |||
| 444711b049 | |||
| 0a7abb1389 | |||
| 998a799c3a | |||
| ef8f3ebe6c | |||
| 92976fbf27 | |||
| e7e7f039b8 | |||
| 0d436d8190 | |||
| 3fdbaf0285 | |||
| 8a049efe49 | |||
| a62b2214c4 | |||
| f00e665a03 | |||
| 454d83de2e | |||
| 30cc0b79c5 | |||
| 775d9158a6 | |||
| c60429ad28 | |||
| 0cf9602958 | |||
| e2fb616565 | |||
| faaf12b6a4 | |||
| 1a69a20750 | |||
| 71cb09f051 | |||
| b668195e21 | |||
| 94934027fa | |||
| 120c8873ef | |||
| 188735e4aa | |||
| b291c05da7 | |||
| adffa6d6b2 | |||
| 4357fd022d | |||
| d6416d9c6b | |||
| 7c1125e213 | |||
| b91af2e889 | |||
| 4a97cf230b | |||
| 0c7e3ab365 | |||
| 222113b0d5 | |||
| 895e44c984 | |||
| ef3d03200e | |||
| 6c891ff64a | |||
| d9ba5ab4f1 | |||
| 1816ca5165 | |||
| dff7d65476 | |||
| ce57669263 | |||
| 2444aab204 | |||
| 7b2f2de1f0 | |||
| 94b1d7b2ac | |||
| 1f5f6bc32e | |||
| 9e39435818 | |||
| 096ba07b4c | |||
| 5bcdc2fb5d | |||
| f4ea07d82c | |||
| 21be212129 | |||
| 8348f677a5 | |||
| f813056bf7 | |||
| 1ee6b9968f | |||
| 6f8be58943 | |||
| 2e8daf78e1 | |||
| 4878f750bd | |||
| 33a9271013 | |||
| ff98f0860c | |||
| ed7f887e3a | |||
| e6bd8c684d | |||
| 653e85b781 | |||
| 43f8621053 | |||
| 2d17e61cc8 | |||
| 7bea223ded | |||
| df7c14442e | |||
| 33b4a249cc | |||
| 37039f082c | |||
| 405591d6dd | |||
| b8341890d3 | |||
| 23af267896 | |||
| b1c333648a | |||
| fcf41c5d13 | |||
| 2b458eccd7 | |||
| 4f4dff2edd | |||
| cd526231ed | |||
| fa886aad4d | |||
| f468814a2f | |||
| ab711109a7 | |||
| 72623df38f | |||
| 9fd6839878 | |||
| fd403f8520 | |||
| 882752472e | |||
| 87531237c7 | |||
| ee7fc637f1 | |||
| bccfc11687 | |||
| 6fc65e195c | |||
| e9ae850002 | |||
| 11108bdfcc | |||
| e280fcfba8 | |||
| 3570f442f5 |
@@ -0,0 +1,81 @@
|
||||
# Projektkonventionen
|
||||
|
||||
## Architektur: Actions (Request-Command-Response)
|
||||
|
||||
Jede fachliche Operation wird in eine eigene Action ausgelagert, die aus drei Klassen besteht.
|
||||
Pfad: `app/Domains/{Domain}/Actions/{ActionName}/`
|
||||
|
||||
### Struktur
|
||||
{ActionName}Request.php → Eingabedaten (Konstruktor oder Factory-Methoden) {ActionName}Command.php → Logik, ruft execute(): {ActionName}Response auf {ActionName}Response.php → Rückgabedaten (public Properties)
|
||||
|
||||
|
||||
### Regeln
|
||||
- Der Controller enthält **keine** fachliche Logik – nur Absicherung, Action-Aufruf und HTTP-Response
|
||||
- Commands sind nicht statisch und werden immer instanziiert
|
||||
- Hat ein Request mehrere Varianten, werden **Factory-Methoden** (`forX()`) statt mehrerer Konstruktoren verwendet
|
||||
- Aufrufreihenfolge im Controller: `new Request → new Command(request) → command->execute() → Response verwenden`
|
||||
|
||||
---
|
||||
|
||||
## Controller
|
||||
|
||||
- Alle Controller erben von `App\Scopes\CommonController`
|
||||
- `CommonController` stellt folgende Repositories bereit (keine eigene Instanziierung nötig):
|
||||
- `$this->eventParticipants` → `EventParticipantRepository`
|
||||
- `$this->events` → `EventRepository`
|
||||
- `$this->invoices` → `InvoiceRepository`
|
||||
- `$this->costUnits` → `CostUnitRepository`
|
||||
- `$this->users` → `UserRepository`
|
||||
- `$this->tenant` → aktueller `Tenant`
|
||||
|
||||
- Die Controller besitzen ausschließlich eine __invoke() - Funktion
|
||||
- Für die Speichern-Actions werden separate Controller-Klassen erstellt (z. B. `StoreEventParticipantController`)
|
||||
---
|
||||
|
||||
## Repositories
|
||||
|
||||
- Datenbankzugriffe gehören **immer** ins Repository, nie direkt in Controller oder Actions
|
||||
- Sicherheitschecks (z. B. „gehört diese Teilnahme dem eingeloggten User?") werden als eigene Repository-Methoden gekapselt
|
||||
- Tenant-Filter: `app('tenant')->slug`
|
||||
- Eingeloggter User: `auth()->user()`
|
||||
|
||||
---
|
||||
|
||||
## Models / Ressourcen
|
||||
|
||||
- Models erben von `App\Scopes\InstancedModel` (mit globalem `SiteScope`)
|
||||
- `$model->toResource()->toArray($request)` liefert das aufbereitete Array über die zugehörige Resource-Klasse
|
||||
- Resource-Klassen liegen in `app/Resources/{ModelName}Resource.php`
|
||||
|
||||
---
|
||||
|
||||
## Tenant
|
||||
|
||||
- Der aktuelle Tenant ist per `app('tenant')` verfügbar (gesetzt durch `IdentifyTenant`-Middleware)
|
||||
- Tenant-Slug: `app('tenant')->slug`
|
||||
- Jede tenant-spezifische DB-Abfrage filtert auf `['tenant' => app('tenant')->slug]`
|
||||
|
||||
---
|
||||
|
||||
## Routing
|
||||
|
||||
- API-Routen liegen in `app/Domains/{Domain}/Routes/api.php`
|
||||
- Alle Routen sind in `IdentifyTenant::class`-Middleware gewrappt
|
||||
- Authentifizierte Routen zusätzlich in `['auth']`-Middleware
|
||||
|
||||
---
|
||||
|
||||
## Mails
|
||||
|
||||
- Mails erben von `Illuminate\Mail\Mailable`
|
||||
- Attachments werden über `Attachment::fromData(fn () => $content, $filename)->withMime(...)` angehängt
|
||||
- Werden Daten sowohl in `content()` als auch in `attachments()` benötigt, wird eine **private Hilfsmethode mit Lazy-Caching** verwendet (einmaliges Berechnen, Ergebnis in private Property speichern)
|
||||
- Blade-Templates referenzieren Mail-Attachments per `cid:`-Link: `<a href="cid:{{ $filename }}">...</a>`
|
||||
|
||||
## Actions
|
||||
- Die Actions sind in `app/Domains/{Domain}/Actions/{ActionName}/`-Verzeichnissen organisiert
|
||||
- Jede Action besitzt einen Request, einen Command und einen Response
|
||||
- Der Request besitzt auschließlich einen Konstruktor, der die notwendigen Parameter annimmt
|
||||
- Die Response-Klasse enthält ausschließlich die notwendigen Daten für die Antwort
|
||||
- Die Action-Klasse enthält die Logik für die Verarbeitung der Anfrage und die Generierung der Antwort
|
||||
- Die Logik wird in einer execute() - Funktion innerhalb des Commands impplementiert. Private Funktionen, für ausgelagerte Prozesse sind zulässig, wenn der Code damit lesbarer wird.
|
||||
@@ -0,0 +1,5 @@
|
||||
.ai
|
||||
.junie
|
||||
.git
|
||||
storage
|
||||
tests
|
||||
@@ -22,3 +22,4 @@
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
/docker-compose.yaml
|
||||
|
||||
@@ -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.
|
||||
@@ -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 && \
|
||||
|
||||
@@ -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,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||
|
||||
use App\Models\Tenant;
|
||||
|
||||
class CreateTenantAction
|
||||
{
|
||||
public function __construct(private CreateTenantRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): CreateTenantResponse
|
||||
{
|
||||
$response = new CreateTenantResponse();
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'name' => $this->request->name,
|
||||
'slug' => $this->request->slug,
|
||||
'url' => $this->request->url,
|
||||
'email' => '',
|
||||
'email_finance' => '',
|
||||
'account_name' => '',
|
||||
'account_iban' => '',
|
||||
'account_bic' => '',
|
||||
'city' => '',
|
||||
'postcode' => '',
|
||||
'is_active_local_group' => true,
|
||||
'has_active_instance' => true,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Stamm wurde angelegt.';
|
||||
$response->slug = $tenant->slug;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||
|
||||
class CreateTenantRequest
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $slug,
|
||||
public string $url,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||
|
||||
class CreateTenantResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
public ?string $slug = null;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\ToggleUserActive;
|
||||
|
||||
class ToggleUserActiveAction
|
||||
{
|
||||
public function __construct(private ToggleUserActiveRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): ToggleUserActiveResponse
|
||||
{
|
||||
$response = new ToggleUserActiveResponse();
|
||||
|
||||
if ($this->request->currentUserId === $this->request->user->id) {
|
||||
$response->message = 'Du kannst dich nicht selbst deaktivieren.';
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->request->user->update(['active' => !$this->request->user->active]);
|
||||
|
||||
$status = $this->request->user->active ? 'aktiviert' : 'deaktiviert';
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Benutzer*in wurde ' . $status . '.';
|
||||
$response->active = $this->request->user->active;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\ToggleUserActive;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class ToggleUserActiveRequest
|
||||
{
|
||||
public function __construct(
|
||||
public User $user,
|
||||
public int $currentUserId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\ToggleUserActive;
|
||||
|
||||
class ToggleUserActiveResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
public ?bool $active = null;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantContact;
|
||||
|
||||
class UpdateTenantContactAction
|
||||
{
|
||||
public function __construct(private UpdateTenantContactRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateTenantContactResponse
|
||||
{
|
||||
$response = new UpdateTenantContactResponse();
|
||||
|
||||
$this->request->tenant->update([
|
||||
'email' => $this->request->email,
|
||||
'email_finance' => $this->request->emailFinance,
|
||||
'postcode' => $this->request->postcode,
|
||||
'city' => $this->request->city,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Kontaktdaten wurden gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantContact;
|
||||
|
||||
use App\Models\Tenant;
|
||||
|
||||
class UpdateTenantContactRequest
|
||||
{
|
||||
public function __construct(
|
||||
public Tenant $tenant,
|
||||
public string $email,
|
||||
public string $emailFinance,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantContact;
|
||||
|
||||
class UpdateTenantContactResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGdpr;
|
||||
|
||||
class UpdateTenantGdprAction
|
||||
{
|
||||
public function __construct(private UpdateTenantGdprRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateTenantGdprResponse
|
||||
{
|
||||
$response = new UpdateTenantGdprResponse();
|
||||
|
||||
$this->request->tenant->update([
|
||||
'gdpr_text' => $this->request->gdprText,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Datenschutzerklärung wurde gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGdpr;
|
||||
|
||||
use App\Models\Tenant;
|
||||
|
||||
class UpdateTenantGdprRequest
|
||||
{
|
||||
public function __construct(
|
||||
public Tenant $tenant,
|
||||
public string $gdprText,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGdpr;
|
||||
|
||||
class UpdateTenantGdprResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||
|
||||
class UpdateTenantGeneralAction
|
||||
{
|
||||
public function __construct(private UpdateTenantGeneralRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateTenantGeneralResponse
|
||||
{
|
||||
$response = new UpdateTenantGeneralResponse();
|
||||
|
||||
$this->request->tenant->update([
|
||||
'name' => $this->request->name,
|
||||
'slug' => $this->request->slug,
|
||||
'url' => $this->request->url,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Allgemeine Daten wurden gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||
|
||||
use App\Models\Tenant;
|
||||
|
||||
class UpdateTenantGeneralRequest
|
||||
{
|
||||
public function __construct(
|
||||
public Tenant $tenant,
|
||||
public string $name,
|
||||
public string $slug,
|
||||
public string $url,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||
|
||||
class UpdateTenantGeneralResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantImpress;
|
||||
|
||||
class UpdateTenantImpressAction
|
||||
{
|
||||
public function __construct(private UpdateTenantImpressRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateTenantImpressResponse
|
||||
{
|
||||
$response = new UpdateTenantImpressResponse();
|
||||
|
||||
$this->request->tenant->update([
|
||||
'impress_text' => $this->request->impressText,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Impressum wurde gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantImpress;
|
||||
|
||||
use App\Models\Tenant;
|
||||
|
||||
class UpdateTenantImpressRequest
|
||||
{
|
||||
public function __construct(
|
||||
public Tenant $tenant,
|
||||
public string $impressText,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantImpress;
|
||||
|
||||
class UpdateTenantImpressResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||
|
||||
class UpdateTenantPaymentAction
|
||||
{
|
||||
public function __construct(private UpdateTenantPaymentRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateTenantPaymentResponse
|
||||
{
|
||||
$response = new UpdateTenantPaymentResponse();
|
||||
|
||||
$this->request->tenant->update([
|
||||
'account_iban' => $this->request->accountIban,
|
||||
'account_bic' => $this->request->accountBic,
|
||||
'account_name' => $this->request->accountName,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Bezahldaten wurden gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||
|
||||
use App\Models\Tenant;
|
||||
|
||||
class UpdateTenantPaymentRequest
|
||||
{
|
||||
public function __construct(
|
||||
public Tenant $tenant,
|
||||
public string $accountIban,
|
||||
public string $accountBic,
|
||||
public string $accountName,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||
|
||||
class UpdateTenantPaymentResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateUser;
|
||||
|
||||
class UpdateUserAction
|
||||
{
|
||||
public function __construct(private UpdateUserRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateUserResponse
|
||||
{
|
||||
$response = new UpdateUserResponse();
|
||||
|
||||
$allowedFields = [
|
||||
'firstname', 'lastname', 'nickname', 'email', 'phone', 'birthday',
|
||||
'membership_id', 'address_1', 'address_2', 'postcode', 'city',
|
||||
'eating_habits', 'swimming_permission', 'first_aid_permission',
|
||||
'bank_account_owner', 'bank_account_iban',
|
||||
'medications', 'allergies', 'intolerances',
|
||||
'user_role_local_group',
|
||||
];
|
||||
|
||||
if ($this->request->isLvTenant) {
|
||||
$allowedFields[] = 'local_group';
|
||||
if (!$this->request->isOwnUser) {
|
||||
$allowedFields[] = 'user_role_main';
|
||||
}
|
||||
}
|
||||
|
||||
$data = array_intersect_key($this->request->data, array_flip($allowedFields));
|
||||
$this->request->user->update($data);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Benutzerdaten wurden gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateUser;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UpdateUserRequest
|
||||
{
|
||||
public function __construct(
|
||||
public User $user,
|
||||
public array $data,
|
||||
public bool $isOwnUser,
|
||||
public bool $isLvTenant,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateUser;
|
||||
|
||||
class UpdateUserResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class AdminDashboardController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Admin/Dashboard', []);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantContactGetController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
return response()->json([
|
||||
'email' => $tenant->email,
|
||||
'email_finance' => $tenant->email_finance,
|
||||
'postcode' => $tenant->postcode,
|
||||
'city' => $tenant->city,
|
||||
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/contact',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantContactUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
$action = new UpdateTenantContactAction(new UpdateTenantContactRequest(
|
||||
tenant: $tenant,
|
||||
email: $request->input('email'),
|
||||
emailFinance: $request->input('email_finance'),
|
||||
postcode: $request->input('postcode'),
|
||||
city: $request->input('city'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantGdprGetController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
return response()->json([
|
||||
'gdpr_text' => $tenant->gdpr_text ?? '',
|
||||
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/gdpr',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantGdprUpdateController extends CommonController
|
||||
{
|
||||
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
$action = new UpdateTenantGdprAction(new UpdateTenantGdprRequest(
|
||||
tenant: $tenant,
|
||||
gdprText: $request->input('gdpr_text'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantImpressGetController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
return response()->json([
|
||||
'impress_text' => $tenant->impress_text ?? '',
|
||||
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/impress',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantImpressUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
$action = new UpdateTenantImpressAction(new UpdateTenantImpressRequest(
|
||||
tenant: $tenant,
|
||||
impressText: $request->input('impress_text'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantPaymentGetController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
return response()->json([
|
||||
'account_iban' => $tenant->account_iban,
|
||||
'account_bic' => $tenant->account_bic,
|
||||
'account_name' => $tenant->account_name,
|
||||
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/payment',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ManagedTenantPaymentUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
$action = new UpdateTenantPaymentAction(new UpdateTenantPaymentRequest(
|
||||
tenant: $tenant,
|
||||
accountIban: $request->input('account_iban'),
|
||||
accountBic: $request->input('account_bic'),
|
||||
accountName: $request->input('account_name'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantContactGetController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'email' => $this->tenant->email,
|
||||
'email_finance' => $this->tenant->email_finance,
|
||||
'postcode' => $this->tenant->postcode,
|
||||
'city' => $this->tenant->city,
|
||||
'saveEndpoint' => '/api/v1/admin/tenant/contact',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantContactUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$action = new UpdateTenantContactAction(new UpdateTenantContactRequest(
|
||||
tenant: $this->tenant,
|
||||
email: $request->input('email'),
|
||||
emailFinance: $request->input('email_finance'),
|
||||
postcode: $request->input('postcode'),
|
||||
city: $request->input('city'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\CreateTenant\CreateTenantAction;
|
||||
use App\Domains\Admin\Actions\CreateTenant\CreateTenantRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantCreateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$action = new CreateTenantAction(new CreateTenantRequest(
|
||||
name: $request->input('name'),
|
||||
slug: $request->input('slug'),
|
||||
url: $request->input('url'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
'slug' => $response->slug,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class TenantEditPageController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): Response
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Admin/TenantEdit', [
|
||||
'tenant' => [
|
||||
'name' => $tenant->name,
|
||||
'slug' => $tenant->slug,
|
||||
'url' => $tenant->url,
|
||||
'is_active_local_group' => $tenant->is_active_local_group,
|
||||
],
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantGdprGetController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'gdpr_text' => $this->tenant->gdpr_text ?? '',
|
||||
'saveEndpoint' => '/api/v1/admin/tenant/gdpr',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantGdprUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$action = new UpdateTenantGdprAction(new UpdateTenantGdprRequest(
|
||||
tenant: $this->tenant,
|
||||
gdprText: $request->input('gdpr_text'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantGeneralGetController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
return response()->json([
|
||||
'name' => $tenant->name,
|
||||
'slug' => $tenant->slug,
|
||||
'url' => $tenant->url,
|
||||
'is_active_local_group' => $tenant->is_active_local_group,
|
||||
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/general',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantGeneral\UpdateTenantGeneralAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantGeneral\UpdateTenantGeneralRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantGeneralUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(string $slug, Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $this->adminTenants->findBySlug($slug);
|
||||
|
||||
$action = new UpdateTenantGeneralAction(new UpdateTenantGeneralRequest(
|
||||
tenant: $tenant,
|
||||
name: $request->input('name'),
|
||||
slug: $request->input('slug'),
|
||||
url: $request->input('url'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantImpressGetController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'impress_text' => $this->tenant->impress_text ?? '',
|
||||
'saveEndpoint' => '/api/v1/admin/tenant/impress',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantImpressUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$action = new UpdateTenantImpressAction(new UpdateTenantImpressRequest(
|
||||
tenant: $this->tenant,
|
||||
impressText: $request->input('impress_text'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantListApiController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$tenants = $this->adminTenants->getActiveTenants();
|
||||
|
||||
$mapped = $tenants->map(function ($tenant) {
|
||||
return [
|
||||
'name' => $tenant->name,
|
||||
'slug' => $tenant->slug,
|
||||
'url' => $tenant->url,
|
||||
'is_active_local_group' => $tenant->is_active_local_group,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json(['tenants' => $mapped]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class TenantListPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Admin/TenantList', []);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class TenantPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Admin/TenantData', [
|
||||
'tenant' => [
|
||||
'name' => $this->tenant->name,
|
||||
'slug' => $this->tenant->slug,
|
||||
'url' => $this->tenant->url,
|
||||
'is_active_local_group' => $this->tenant->is_active_local_group,
|
||||
],
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantPaymentGetController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'account_iban' => $this->tenant->account_iban,
|
||||
'account_bic' => $this->tenant->account_bic,
|
||||
'account_name' => $this->tenant->account_name,
|
||||
'saveEndpoint' => '/api/v1/admin/tenant/payment',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentAction;
|
||||
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TenantPaymentUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$action = new UpdateTenantPaymentAction(new UpdateTenantPaymentRequest(
|
||||
tenant: $this->tenant,
|
||||
accountIban: $request->input('account_iban'),
|
||||
accountBic: $request->input('account_bic'),
|
||||
accountName: $request->input('account_name'),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Enumerations\UserRole;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserDetailGetController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->adminUsers->findById($id);
|
||||
|
||||
$userData = $user->toArray();
|
||||
unset($userData['password'], $userData['remember_token'], $userData['activation_token'], $userData['activation_token_expires_at']);
|
||||
|
||||
$tenantNames = $this->adminTenants->getTenantNames();
|
||||
$userData['nicename'] = $user->getNicename();
|
||||
$userData['fullname'] = $user->getFullName();
|
||||
$userData['local_group_name'] = $tenantNames[$user->local_group] ?? $user->local_group;
|
||||
|
||||
return response()->json([
|
||||
'user' => $userData,
|
||||
'isOwnUser' => auth()->id() === $user->id,
|
||||
'isLvTenant' => $this->tenant->slug === 'lv',
|
||||
'userRoles' => UserRole::all()->map(fn($role) => ['slug' => $role->slug, 'name' => $role->name]),
|
||||
'localGroups' => $this->adminTenants->getActiveLocalGroups()->map(fn($t) => ['slug' => $t->slug, 'name' => $t->name]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserListApiController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$tenantNames = $this->adminTenants->getTenantNames();
|
||||
$users = $this->adminUsers->getListForTenant($this->tenant->slug);
|
||||
|
||||
$mapped = $users->map(function ($user) use ($tenantNames) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'firstname' => $user->firstname,
|
||||
'lastname' => $user->lastname,
|
||||
'nickname' => $user->nickname,
|
||||
'local_group' => $user->local_group,
|
||||
'local_group_name' => $tenantNames[$user->local_group] ?? $user->local_group,
|
||||
'active' => $user->active,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json(['users' => $mapped]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class UserListPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Admin/UserList', [
|
||||
'isLvTenant' => $this->tenant->slug === 'lv',
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserResetPasswordController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->adminUsers->findById($id);
|
||||
|
||||
if (!$user->email) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Benutzer*in hat keine E-Mail-Adresse hinterlegt.',
|
||||
]);
|
||||
}
|
||||
|
||||
$command = new GenerateActivationTokenCommand($user);
|
||||
$command->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Passwort-Reset-Mail wurde gesendet.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\ToggleUserActive\ToggleUserActiveAction;
|
||||
use App\Domains\Admin\Actions\ToggleUserActive\ToggleUserActiveRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserToggleActiveController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->adminUsers->findById($id);
|
||||
|
||||
$action = new ToggleUserActiveAction(new ToggleUserActiveRequest(
|
||||
user: $user,
|
||||
currentUserId: auth()->id(),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
'active' => $response->active,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateUser\UpdateUserAction;
|
||||
use App\Domains\Admin\Actions\UpdateUser\UpdateUserRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->adminUsers->findById($id);
|
||||
|
||||
$action = new UpdateUserAction(new UpdateUserRequest(
|
||||
user: $user,
|
||||
data: $request->all(),
|
||||
isOwnUser: auth()->id() === $user->id,
|
||||
isLvTenant: $this->tenant->slug === 'lv',
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Repositories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
|
||||
class AdminTenantRepository
|
||||
{
|
||||
public function findBySlug(string $slug): Tenant
|
||||
{
|
||||
return Tenant::where('slug', $slug)->firstOrFail();
|
||||
}
|
||||
|
||||
public function getActiveTenants(): Collection
|
||||
{
|
||||
return Tenant::where('has_active_instance', true)->get();
|
||||
}
|
||||
|
||||
public function getTenantNames(): SupportCollection
|
||||
{
|
||||
return Tenant::pluck('name', 'slug');
|
||||
}
|
||||
|
||||
public function getActiveLocalGroups(): Collection
|
||||
{
|
||||
return Tenant::where('is_active_local_group', true)->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Repositories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class AdminUserRepository
|
||||
{
|
||||
public function findById(int $id): User
|
||||
{
|
||||
return User::findOrFail($id);
|
||||
}
|
||||
|
||||
public function getListForTenant(string $tenantSlug): Collection
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
if ($tenantSlug !== 'lv') {
|
||||
$query->where('local_group', $tenantSlug);
|
||||
}
|
||||
|
||||
return $query->orderBy('lastname')->orderBy('firstname')->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Admin\Controllers\UserDetailGetController;
|
||||
use App\Domains\Admin\Controllers\UserListApiController;
|
||||
use App\Domains\Admin\Controllers\UserResetPasswordController;
|
||||
use App\Domains\Admin\Controllers\UserToggleActiveController;
|
||||
use App\Domains\Admin\Controllers\UserUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantContactGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantContactUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantGdprGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantGdprUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantImpressGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantImpressUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantPaymentGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantPaymentUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantContactGetController;
|
||||
use App\Domains\Admin\Controllers\TenantContactUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantCreateController;
|
||||
use App\Domains\Admin\Controllers\TenantGdprGetController;
|
||||
use App\Domains\Admin\Controllers\TenantGdprUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantGeneralGetController;
|
||||
use App\Domains\Admin\Controllers\TenantGeneralUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantImpressGetController;
|
||||
use App\Domains\Admin\Controllers\TenantImpressUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantListApiController;
|
||||
use App\Domains\Admin\Controllers\TenantPaymentGetController;
|
||||
use App\Domains\Admin\Controllers\TenantPaymentUpdateController;
|
||||
use App\Middleware\AdminRoleMiddleware;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use App\Middleware\LvOnlyMiddleware;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->group(function () {
|
||||
Route::prefix('api/v1/admin/tenant')->group(function () {
|
||||
Route::get('/contact', TenantContactGetController::class);
|
||||
Route::post('/contact', TenantContactUpdateController::class);
|
||||
Route::get('/payment', TenantPaymentGetController::class);
|
||||
Route::post('/payment', TenantPaymentUpdateController::class);
|
||||
Route::get('/impress', TenantImpressGetController::class);
|
||||
Route::post('/impress', TenantImpressUpdateController::class);
|
||||
Route::get('/gdpr', TenantGdprGetController::class);
|
||||
Route::post('/gdpr', TenantGdprUpdateController::class);
|
||||
});
|
||||
|
||||
Route::prefix('api/v1/admin/users')->group(function () {
|
||||
Route::get('/list', UserListApiController::class);
|
||||
Route::get('/{id}', UserDetailGetController::class);
|
||||
Route::post('/{id}', UserUpdateController::class);
|
||||
Route::post('/{id}/toggle-active', UserToggleActiveController::class);
|
||||
Route::post('/{id}/reset-password', UserResetPasswordController::class);
|
||||
});
|
||||
|
||||
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||
Route::prefix('api/v1/admin/tenants')->group(function () {
|
||||
Route::get('/list', TenantListApiController::class);
|
||||
Route::post('/create', TenantCreateController::class);
|
||||
Route::prefix('/{slug}')->group(function () {
|
||||
Route::get('/general', TenantGeneralGetController::class);
|
||||
Route::post('/general', TenantGeneralUpdateController::class);
|
||||
Route::get('/contact', ManagedTenantContactGetController::class);
|
||||
Route::post('/contact', ManagedTenantContactUpdateController::class);
|
||||
Route::get('/payment', ManagedTenantPaymentGetController::class);
|
||||
Route::post('/payment', ManagedTenantPaymentUpdateController::class);
|
||||
Route::get('/impress', ManagedTenantImpressGetController::class);
|
||||
Route::post('/impress', ManagedTenantImpressUpdateController::class);
|
||||
Route::get('/gdpr', ManagedTenantGdprGetController::class);
|
||||
Route::post('/gdpr', ManagedTenantGdprUpdateController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Admin\Controllers\AdminDashboardController;
|
||||
use App\Domains\Admin\Controllers\TenantEditPageController;
|
||||
use App\Domains\Admin\Controllers\TenantListPageController;
|
||||
use App\Domains\Admin\Controllers\TenantPageController;
|
||||
use App\Domains\Admin\Controllers\UserListPageController;
|
||||
use App\Middleware\AdminRoleMiddleware;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use App\Middleware\LvOnlyMiddleware;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->group(function () {
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::get('/', AdminDashboardController::class);
|
||||
Route::get('/tenant', TenantPageController::class);
|
||||
Route::get('/users', UserListPageController::class);
|
||||
|
||||
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||
Route::get('/tenants', TenantListPageController::class);
|
||||
Route::get('/tenants/{slug}', TenantEditPageController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Administration">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<h2>Administration</h2>
|
||||
</shadowed-box>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const editing = ref(false)
|
||||
const form = ref({
|
||||
email: props.data.email ?? '',
|
||||
email_finance: props.data.email_finance ?? '',
|
||||
postcode: props.data.postcode ?? '',
|
||||
city: props.data.city ?? '',
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/contact'
|
||||
const response = await request(saveUrl, {
|
||||
method: 'POST',
|
||||
body: form.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
editing.value = false
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!editing">
|
||||
<table class="data-table">
|
||||
<tr><th>Email:</th><td>{{ form.email }}</td></tr>
|
||||
<tr><th>Email Schatzmeister*in:</th><td>{{ form.email_finance }}</td></tr>
|
||||
<tr><th>Postleitzahl:</th><td>{{ form.postcode }}</td></tr>
|
||||
<tr><th>Ort:</th><td>{{ form.city }}</td></tr>
|
||||
</table>
|
||||
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="data-table">
|
||||
<tr>
|
||||
<th>Email:</th>
|
||||
<td><input type="email" v-model="form.email" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email Schatzmeister*in:</th>
|
||||
<td><input type="email" v-model="form.email_finance" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Postleitzahl:</th>
|
||||
<td><input type="text" v-model="form.postcode" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Ort:</th>
|
||||
<td><input type="text" v-model="form.city" class="form-input" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
width: 200px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-save, .btn-cancel {
|
||||
margin-top: 15px;
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
import gdprTemplate from "../../../../../resources/templates/gdpr-template.html?raw";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const content = ref(props.data.gdpr_text ?? '')
|
||||
|
||||
function autoGenerate() {
|
||||
if (content.value && content.value.trim() !== '') {
|
||||
toast.error('Der Editor ist nicht leer. Bitte leere den Inhalt zuerst, um die Vorlage zu verwenden.')
|
||||
return
|
||||
}
|
||||
|
||||
const today = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
content.value = gdprTemplate.replace('[Datum]', today)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/gdpr'
|
||||
const response = await request(saveUrl, {
|
||||
method: 'POST',
|
||||
body: { gdpr_text: content.value },
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<TextEditor v-model="content" />
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
<button class="btn-generate" @click="autoGenerate">Auto-generieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.btn-save, .btn-generate {
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-generate {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-generate:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const editing = ref(false)
|
||||
const form = ref({
|
||||
name: props.data.name ?? '',
|
||||
slug: props.data.slug ?? '',
|
||||
url: props.data.url ?? '',
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenants/' + props.data.slug + '/general'
|
||||
const response = await request(saveUrl, {
|
||||
method: 'POST',
|
||||
body: form.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
editing.value = false
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!editing">
|
||||
<table class="data-table">
|
||||
<tr><th>Name:</th><td>{{ form.name }}</td></tr>
|
||||
<tr><th>Slug:</th><td>{{ form.slug }}</td></tr>
|
||||
<tr><th>URL:</th><td>{{ form.url }}</td></tr>
|
||||
<tr><th>Status:</th><td>
|
||||
<span class="badge-active">Aktiv</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="data-table">
|
||||
<tr>
|
||||
<th>Name:</th>
|
||||
<td><input type="text" v-model="form.name" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Slug:</th>
|
||||
<td><input type="text" v-model="form.slug" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>URL:</th>
|
||||
<td><input type="text" v-model="form.url" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr><th>Status:</th><td>
|
||||
<span class="badge-active">Aktiv</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
width: 200px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-save, .btn-cancel {
|
||||
margin-top: 15px;
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const content = ref(props.data.impress_text ?? '')
|
||||
|
||||
async function save() {
|
||||
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/impress'
|
||||
const response = await request(saveUrl, {
|
||||
method: 'POST',
|
||||
body: { impress_text: content.value },
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<TextEditor v-model="content" />
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-save {
|
||||
margin-top: 15px;
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,140 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const editing = ref(false)
|
||||
const form = ref({
|
||||
account_iban: props.data.account_iban ?? '',
|
||||
account_bic: props.data.account_bic ?? '',
|
||||
account_name: props.data.account_name ?? '',
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/payment'
|
||||
const response = await request(saveUrl, {
|
||||
method: 'POST',
|
||||
body: form.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
editing.value = false
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!editing">
|
||||
<table class="data-table">
|
||||
<tr><th>IBAN:</th><td>{{ form.account_iban }}</td></tr>
|
||||
<tr><th>BIC:</th><td>{{ form.account_bic }}</td></tr>
|
||||
<tr><th>Name Kontoinhaber:</th><td>{{ form.account_name }}</td></tr>
|
||||
</table>
|
||||
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="data-table">
|
||||
<tr>
|
||||
<th>IBAN:</th>
|
||||
<td><input type="text" v-model="form.account_iban" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>BIC:</th>
|
||||
<td><input type="text" v-model="form.account_bic" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name Kontoinhaber:</th>
|
||||
<td><input type="text" v-model="form.account_name" class="form-input" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
width: 200px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-save, .btn-cancel {
|
||||
margin-top: 15px;
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,331 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updated', 'closed'])
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const editing = ref(false)
|
||||
const user = props.data.user
|
||||
const isOwnUser = props.data.isOwnUser
|
||||
const isLvTenant = props.data.isLvTenant
|
||||
const userRoles = props.data.userRoles
|
||||
const localGroups = props.data.localGroups
|
||||
|
||||
const form = ref({
|
||||
firstname: user.firstname ?? '',
|
||||
lastname: user.lastname ?? '',
|
||||
nickname: user.nickname ?? '',
|
||||
email: user.email ?? '',
|
||||
phone: user.phone ?? '',
|
||||
birthday: user.birthday ?? '',
|
||||
membership_id: user.membership_id ?? '',
|
||||
address_1: user.address_1 ?? '',
|
||||
address_2: user.address_2 ?? '',
|
||||
postcode: user.postcode ?? '',
|
||||
city: user.city ?? '',
|
||||
medications: user.medications ?? '',
|
||||
allergies: user.allergies ?? '',
|
||||
intolerances: user.intolerances ?? '',
|
||||
bank_account_owner: user.bank_account_owner ?? '',
|
||||
bank_account_iban: user.bank_account_iban ?? '',
|
||||
user_role_local_group: user.user_role_local_group ?? '',
|
||||
user_role_main: user.user_role_main ?? '',
|
||||
local_group: user.local_group ?? '',
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const response = await request('/api/v1/admin/users/' + user.id, {
|
||||
method: 'POST',
|
||||
body: form.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
editing.value = false
|
||||
emit('updated')
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleActive() {
|
||||
const response = await request('/api/v1/admin/users/' + user.id + '/toggle-active', {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
emit('updated')
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler')
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword() {
|
||||
const response = await request('/api/v1/admin/users/' + user.id + '/reset-password', {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler')
|
||||
}
|
||||
}
|
||||
|
||||
function getRoleName(slug) {
|
||||
const role = userRoles.find(r => r.slug === slug)
|
||||
return role ? role.name : slug
|
||||
}
|
||||
|
||||
function getGroupName(slug) {
|
||||
const group = localGroups.find(g => g.slug === slug)
|
||||
return group ? group.name : slug
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="detail-header">
|
||||
<h3>{{ user.firstname }} {{ user.lastname }}</h3>
|
||||
<div class="detail-actions">
|
||||
<button v-if="!editing" class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||
<button class="btn-reset" @click="resetPassword">Passwort zurücksetzen</button>
|
||||
<button v-if="!isOwnUser" :class="user.active ? 'btn-deactivate' : 'btn-activate'" @click="toggleActive">
|
||||
{{ user.active ? 'Deaktivieren' : 'Aktivieren' }}
|
||||
</button>
|
||||
<button class="btn-close" @click="emit('closed')">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!editing">
|
||||
<table class="data-table">
|
||||
<tr><th>Anmeldename:</th><td>{{ user.username }}</td></tr>
|
||||
<tr><th>Vorname:</th><td>{{ form.firstname }}</td></tr>
|
||||
<tr><th>Nachname:</th><td>{{ form.lastname }}</td></tr>
|
||||
<tr><th>Nickname:</th><td>{{ form.nickname }}</td></tr>
|
||||
<tr><th>E-Mail:</th><td>{{ form.email }}</td></tr>
|
||||
<tr><th>Telefon:</th><td>{{ form.phone }}</td></tr>
|
||||
<tr><th>Geburtstag:</th><td>{{ form.birthday }}</td></tr>
|
||||
<tr><th>Mitgliedsnummer:</th><td>{{ form.membership_id }}</td></tr>
|
||||
<tr><th>Adresse 1:</th><td>{{ form.address_1 }}</td></tr>
|
||||
<tr><th>Adresse 2:</th><td>{{ form.address_2 }}</td></tr>
|
||||
<tr><th>PLZ:</th><td>{{ form.postcode }}</td></tr>
|
||||
<tr><th>Ort:</th><td>{{ form.city }}</td></tr>
|
||||
<tr><th>Medikamente:</th><td>{{ form.medications }}</td></tr>
|
||||
<tr><th>Allergien:</th><td>{{ form.allergies }}</td></tr>
|
||||
<tr><th>Unverträglichkeiten:</th><td>{{ form.intolerances }}</td></tr>
|
||||
<tr><th>Kontoinhaber:</th><td>{{ form.bank_account_owner }}</td></tr>
|
||||
<tr><th>IBAN:</th><td>{{ form.bank_account_iban }}</td></tr>
|
||||
<tr><th>Stamm:</th><td>{{ getGroupName(form.local_group) }}</td></tr>
|
||||
<tr><th>Rolle (Stamm):</th><td>{{ getRoleName(form.user_role_local_group) }}</td></tr>
|
||||
<tr><th>Rolle (LV):</th><td>{{ getRoleName(form.user_role_main) }}</td></tr>
|
||||
<tr><th>Status:</th><td>
|
||||
<span :class="user.active ? 'badge-active' : 'badge-inactive'">
|
||||
{{ user.active ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="data-table">
|
||||
<tr><th>Anmeldename:</th><td>{{ user.username }}</td></tr>
|
||||
<tr><th>Vorname:</th><td><input type="text" v-model="form.firstname" class="form-input" /></td></tr>
|
||||
<tr><th>Nachname:</th><td><input type="text" v-model="form.lastname" class="form-input" /></td></tr>
|
||||
<tr><th>Nickname:</th><td><input type="text" v-model="form.nickname" class="form-input" /></td></tr>
|
||||
<tr><th>E-Mail:</th><td><input type="email" v-model="form.email" class="form-input" /></td></tr>
|
||||
<tr><th>Telefon:</th><td><input type="text" v-model="form.phone" class="form-input" /></td></tr>
|
||||
<tr><th>Geburtstag:</th><td><input type="date" v-model="form.birthday" class="form-input" /></td></tr>
|
||||
<tr><th>Mitgliedsnummer:</th><td><input type="text" v-model="form.membership_id" class="form-input" /></td></tr>
|
||||
<tr><th>Adresse 1:</th><td><input type="text" v-model="form.address_1" class="form-input" /></td></tr>
|
||||
<tr><th>Adresse 2:</th><td><input type="text" v-model="form.address_2" class="form-input" /></td></tr>
|
||||
<tr><th>PLZ:</th><td><input type="text" v-model="form.postcode" class="form-input" /></td></tr>
|
||||
<tr><th>Ort:</th><td><input type="text" v-model="form.city" class="form-input" /></td></tr>
|
||||
<tr><th>Medikamente:</th><td><input type="text" v-model="form.medications" class="form-input" /></td></tr>
|
||||
<tr><th>Allergien:</th><td><input type="text" v-model="form.allergies" class="form-input" /></td></tr>
|
||||
<tr><th>Unverträglichkeiten:</th><td><input type="text" v-model="form.intolerances" class="form-input" /></td></tr>
|
||||
<tr><th>Kontoinhaber:</th><td><input type="text" v-model="form.bank_account_owner" class="form-input" /></td></tr>
|
||||
<tr><th>IBAN:</th><td><input type="text" v-model="form.bank_account_iban" class="form-input" /></td></tr>
|
||||
<tr><th>Stamm:</th><td>
|
||||
<select v-if="isLvTenant" v-model="form.local_group" class="form-input">
|
||||
<option v-for="group in localGroups" :key="group.slug" :value="group.slug">{{ group.name }}</option>
|
||||
</select>
|
||||
<span v-else>{{ getGroupName(form.local_group) }}</span>
|
||||
</td></tr>
|
||||
<tr><th>Rolle (Stamm):</th><td>
|
||||
<select v-model="form.user_role_local_group" class="form-input">
|
||||
<option v-for="role in userRoles" :key="role.slug" :value="role.slug">{{ role.name }}</option>
|
||||
</select>
|
||||
</td></tr>
|
||||
<tr><th>Rolle (LV):</th><td>
|
||||
<select v-if="isLvTenant && !isOwnUser" v-model="form.user_role_main" class="form-input">
|
||||
<option v-for="role in userRoles" :key="role.slug" :value="role.slug">{{ role.name }}</option>
|
||||
</select>
|
||||
<span v-else>{{ getRoleName(form.user_role_main) }}</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-header h3 {
|
||||
margin: 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 6px 12px;
|
||||
width: 180px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.badge-inactive {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #991b1b;
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-save, .btn-cancel, .btn-reset, .btn-deactivate, .btn-activate, .btn-close {
|
||||
padding: 6px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background-color: #f59e0b;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-reset:hover {
|
||||
background-color: #d97706;
|
||||
}
|
||||
|
||||
.btn-deactivate {
|
||||
background-color: #ef4444;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-deactivate:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.btn-activate {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-activate:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background-color: #6b7280;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-close:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import TenantContact from "./Partials/TenantContact.vue";
|
||||
import TenantPayment from "./Partials/TenantPayment.vue";
|
||||
import TenantImpress from "./Partials/TenantImpress.vue";
|
||||
import TenantGdpr from "./Partials/TenantGdpr.vue";
|
||||
|
||||
const props = defineProps({
|
||||
tenant: Object,
|
||||
})
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Kontaktdaten',
|
||||
component: TenantContact,
|
||||
endpoint: '/api/v1/admin/tenant/contact',
|
||||
},
|
||||
{
|
||||
title: 'Bezahldaten',
|
||||
component: TenantPayment,
|
||||
endpoint: '/api/v1/admin/tenant/payment',
|
||||
},
|
||||
{
|
||||
title: 'Impressum',
|
||||
component: TenantImpress,
|
||||
endpoint: '/api/v1/admin/tenant/impress',
|
||||
},
|
||||
{
|
||||
title: 'Datenschutzerklärung',
|
||||
component: TenantGdpr,
|
||||
endpoint: '/api/v1/admin/tenant/gdpr',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout :title="props.tenant.slug === 'lv' ? 'LV-Daten' : 'Stammesdaten'">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<table class="tenant-header">
|
||||
<tr><th>Name</th><td>{{ props.tenant.name }}</td></tr>
|
||||
<tr><th>Slug</th><td>{{ props.tenant.slug }}</td></tr>
|
||||
<tr><th>mareike-URL:</th><td>{{ props.tenant.url }}</td></tr>
|
||||
<tr><th>Status</th><td>
|
||||
<span :class="props.tenant.is_active_local_group ? 'badge-active' : 'badge-inactive'">
|
||||
{{ props.tenant.is_active_local_group ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 30px;">
|
||||
<tabbed-page :tabs="tabs" />
|
||||
</div>
|
||||
</shadowed-box>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tenant-header {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tenant-header th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
width: 200px;
|
||||
color: #374151;
|
||||
font-weight: bold;
|
||||
background-color: #f9fafb;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.tenant-header td {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.tenant-header tr:last-child th,
|
||||
.tenant-header tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.badge-inactive {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #991b1b;
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import TenantGeneral from "./Partials/TenantGeneral.vue";
|
||||
import TenantContact from "./Partials/TenantContact.vue";
|
||||
import TenantPayment from "./Partials/TenantPayment.vue";
|
||||
import TenantImpress from "./Partials/TenantImpress.vue";
|
||||
import TenantGdpr from "./Partials/TenantGdpr.vue";
|
||||
|
||||
const props = defineProps({
|
||||
tenant: Object,
|
||||
})
|
||||
|
||||
const slug = props.tenant.slug
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Allgemeines',
|
||||
component: TenantGeneral,
|
||||
endpoint: '/api/v1/admin/tenants/' + slug + '/general',
|
||||
},
|
||||
{
|
||||
title: 'Kontaktdaten',
|
||||
component: TenantContact,
|
||||
endpoint: '/api/v1/admin/tenants/' + slug + '/contact',
|
||||
},
|
||||
{
|
||||
title: 'Bezahldaten',
|
||||
component: TenantPayment,
|
||||
endpoint: '/api/v1/admin/tenants/' + slug + '/payment',
|
||||
},
|
||||
{
|
||||
title: 'Impressum',
|
||||
component: TenantImpress,
|
||||
endpoint: '/api/v1/admin/tenants/' + slug + '/impress',
|
||||
},
|
||||
{
|
||||
title: 'Datenschutzerklärung',
|
||||
component: TenantGdpr,
|
||||
endpoint: '/api/v1/admin/tenants/' + slug + '/gdpr',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout :title="props.tenant.name">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" />
|
||||
</shadowed-box>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,248 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
function openTenant(slug) {
|
||||
window.location.href = '/admin/tenants/' + slug
|
||||
}
|
||||
|
||||
const tenants = ref([])
|
||||
const showCreate = ref(false)
|
||||
const createForm = ref({
|
||||
name: '',
|
||||
slug: '',
|
||||
url: '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await loadTenants()
|
||||
})
|
||||
|
||||
async function loadTenants() {
|
||||
const response = await fetch('/api/v1/admin/tenants/list', {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
const data = await response.json()
|
||||
tenants.value = data.tenants
|
||||
}
|
||||
|
||||
async function createTenant() {
|
||||
const response = await request('/api/v1/admin/tenants/create', {
|
||||
method: 'POST',
|
||||
body: createForm.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
showCreate.value = false
|
||||
createForm.value = { name: '', slug: '', url: '' }
|
||||
await loadTenants()
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Anlegen')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Stämme">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<table class="tenant-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Slug</th>
|
||||
<th>URL</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="tenant in tenants" :key="tenant.slug" class="tenant-row" @click="openTenant(tenant.slug)">
|
||||
<td>{{ tenant.name }}</td>
|
||||
<td>{{ tenant.slug }}</td>
|
||||
<td>{{ tenant.url }}</td>
|
||||
<td>
|
||||
<span :class="tenant.is_active_local_group ? 'badge-active' : 'badge-inactive'">
|
||||
{{ tenant.is_active_local_group ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button v-if="!showCreate" class="btn-create" @click="showCreate = true">Neuen Stamm anlegen</button>
|
||||
|
||||
<div v-if="showCreate" class="create-form">
|
||||
<h3>Neuen Stamm anlegen</h3>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td><input type="text" v-model="createForm.name" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Slug</th>
|
||||
<td><input type="text" v-model="createForm.slug" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<td><input type="text" v-model="createForm.url" class="form-input" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="createTenant">Anlegen</button>
|
||||
<button class="btn-cancel" @click="showCreate = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</shadowed-box>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tenant-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tenant-table thead th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
background-color: #f9fafb;
|
||||
color: #374151;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #d1d5db;
|
||||
}
|
||||
|
||||
.tenant-table tbody td {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.tenant-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tenant-row:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.badge-inactive {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #991b1b;
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
|
||||
.create-form {
|
||||
margin-top: 15px;
|
||||
padding: 20px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.create-form h3 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.form-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
width: 100px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-create {
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-create:hover {
|
||||
background-color: #163a7a;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.btn-save, .btn-cancel {
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #16a34a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,167 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import FullScreenModal from "../../../Views/Components/FullScreenModal.vue";
|
||||
import UserDetail from "./Partials/UserDetail.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const props = defineProps({
|
||||
isLvTenant: Boolean,
|
||||
})
|
||||
|
||||
const users = ref([])
|
||||
const selectedUserId = ref(null)
|
||||
const userDetail = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUsers()
|
||||
})
|
||||
|
||||
async function loadUsers() {
|
||||
const response = await fetch('/api/v1/admin/users/list', {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
const data = await response.json()
|
||||
users.value = data.users
|
||||
}
|
||||
|
||||
async function selectUser(userId) {
|
||||
if (selectedUserId.value === userId) {
|
||||
selectedUserId.value = null
|
||||
userDetail.value = null
|
||||
return
|
||||
}
|
||||
|
||||
selectedUserId.value = userId
|
||||
const response = await fetch('/api/v1/admin/users/' + userId, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
userDetail.value = await response.json()
|
||||
}
|
||||
|
||||
function onUserUpdated() {
|
||||
loadUsers()
|
||||
if (selectedUserId.value) {
|
||||
selectUser(selectedUserId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function onDetailClosed() {
|
||||
selectedUserId.value = null
|
||||
userDetail.value = null
|
||||
loadUsers()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Benutzer*innen">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<table class="user-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nachname</th>
|
||||
<th>Vorname</th>
|
||||
<th>Pfadiname</th>
|
||||
<th v-if="props.isLvTenant">Stamm</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users"
|
||||
:key="user.id"
|
||||
:class="['user-row', { 'user-row-selected': selectedUserId === user.id }]"
|
||||
@click="selectUser(user.id)">
|
||||
<td>{{ user.lastname }}</td>
|
||||
<td>{{ user.firstname }}</td>
|
||||
<td>{{ user.nickname }}</td>
|
||||
<td v-if="props.isLvTenant">{{ user.local_group_name }}</td>
|
||||
<td>
|
||||
<span :class="user.active ? 'badge-active' : 'badge-inactive'">
|
||||
{{ user.active ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</shadowed-box>
|
||||
|
||||
<FullScreenModal :show="userDetail !== null" @close="onDetailClosed">
|
||||
<UserDetail
|
||||
v-if="userDetail"
|
||||
:data="userDetail"
|
||||
@updated="onUserUpdated"
|
||||
@closed="onDetailClosed"
|
||||
/>
|
||||
</FullScreenModal>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-table thead th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
background-color: #f9fafb;
|
||||
color: #374151;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #d1d5db;
|
||||
}
|
||||
|
||||
.user-table tbody td {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.user-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-row:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.user-row-selected {
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #166534;
|
||||
background-color: #dcfce7;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.badge-inactive {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #991b1b;
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Actions\CreateEstimate;
|
||||
|
||||
use App\Models\CostUnitEstimate;
|
||||
|
||||
class CreateEstimateAction {
|
||||
private CreateEstimateResponse $response;
|
||||
|
||||
public function __construct(private CreateEstimateRequest $request) {
|
||||
}
|
||||
|
||||
public function execute(): CreateEstimateResponse {
|
||||
$this->response = new CreateEstimateResponse();
|
||||
|
||||
$amount = [];
|
||||
switch ($this->request->amountType) {
|
||||
case 'flat':
|
||||
$amount['flat_amount'] = $this->request->amount;
|
||||
break;
|
||||
case 'per_person':
|
||||
$amount['amount_by_user'] = $this->request->amount;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->request->estimateId === 0) {
|
||||
$estimate = CostUnitEstimate::create(array_merge([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'cost_unit_id' => $this->request->costUnit->id,
|
||||
'type' => $this->request->estimateType,
|
||||
'description' => $this->request->description,
|
||||
], $amount));
|
||||
} else {
|
||||
$estimate = CostUnitEstimate::find($this->request->estimateId);
|
||||
$estimate->update(array_merge([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'cost_unit_id' => $this->request->costUnit->id,
|
||||
'type' => $this->request->estimateType,
|
||||
'description' => $this->request->description,
|
||||
], $amount));
|
||||
}
|
||||
|
||||
if ($estimate !== null) {
|
||||
$this->response->estimateId = $estimate->id;
|
||||
$this->response->success = true;
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Actions\CreateEstimate;
|
||||
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Models\CostUnit;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class CreateEstimateRequest {
|
||||
function __construct(
|
||||
public string $amountType,
|
||||
public string $description,
|
||||
public Amount $amount,
|
||||
public string $estimateType,
|
||||
public CostUnit $costUnit,
|
||||
public int $estimateId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Actions\CreateEstimate;
|
||||
|
||||
class CreateEstimateResponse {
|
||||
public bool $success;
|
||||
public ?int $estimateId;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->estimateId = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Actions\DeleteEstimate;
|
||||
|
||||
class DeleteEstimateAction {
|
||||
public function __construct(private DeleteEstimateRequest $request) {
|
||||
|
||||
}
|
||||
|
||||
public function execute() : DeleteEstimateResponse {
|
||||
$response = new DeleteEstimateResponse();
|
||||
$this->request->estimate->delete();
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Actions\DeleteEstimate;
|
||||
|
||||
use App\Models\CostUnitEstimate;
|
||||
|
||||
class DeleteEstimateRequest {
|
||||
public function __construct(public CostUnitEstimate $estimate)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Actions\DeleteEstimate;
|
||||
|
||||
class DeleteEstimateResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Controllers;
|
||||
|
||||
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateAction;
|
||||
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateRequest;
|
||||
use App\Domains\Budget\Actions\DeleteEstimate\DeleteEstimateAction;
|
||||
use App\Domains\Budget\Actions\DeleteEstimate\DeleteEstimateRequest;
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DeleteController extends CommonController
|
||||
{
|
||||
public function __invoke(int $costUnitId, int $estimateId, Request $request) : JsonResponse {
|
||||
$estimate = $this->estimates->getById($estimateId);
|
||||
|
||||
if ($estimate === null) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Estimate not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$deleteEstimateResponse =
|
||||
new DeleteEstimateAction(request: new DeleteEstimateRequest($estimate)
|
||||
)->execute();
|
||||
|
||||
if ($deleteEstimateResponse->success) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Der Eintrag wurde erfolgreich gelöscht.'
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Beim Löschen des Eintrags ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Controllers;
|
||||
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListController extends CommonController
|
||||
{
|
||||
public function __invoke(int $costUnitId, string $estimateType, Request $request) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
$estimates = $this->estimates->getEstimates($costUnit, $estimateType);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnitId' => $costUnitId,
|
||||
'title' => InvoiceType::where('slug', $estimateType)->first()->name,
|
||||
'estimateType' => $estimateType,
|
||||
'estimates' => $estimates,
|
||||
'totalAmountString' => $this->estimates->getTotalAmount($costUnit, $estimateType)->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class MainController extends CommonController
|
||||
{
|
||||
public function __invoke(int $costUnitId, Request $request) : Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Budget/List', [
|
||||
'cost_unit_id' => $costUnitId
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Controllers;
|
||||
|
||||
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateAction;
|
||||
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateRequest;
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SaveController extends CommonController
|
||||
{
|
||||
public function __invoke(int $costUnitId, Request $request) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
if ($costUnit === null) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Cost unit not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$createCostUniResponse =
|
||||
new CreateEstimateAction(request: new CreateEstimateRequest(
|
||||
description: $request->input('description'),
|
||||
amount: Amount::fromString($request->input('amount')),
|
||||
amountType: $request->input('amount_type'),
|
||||
estimateType: $request->input('estimateType'),
|
||||
costUnit: $costUnit,
|
||||
estimateId: $request->input('estimateId'),
|
||||
))->execute();
|
||||
|
||||
if ($createCostUniResponse->success) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Der Eintrag wurde erfolgreich angelegt.'
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Beim Anlegen des Eintrags ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Budget\Controllers\DeleteController;
|
||||
use App\Domains\Budget\Controllers\SaveController;
|
||||
use App\Domains\Budget\Controllers\ListController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('api/v1')->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('budget')->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::prefix('/{costUnitId}')->group(function () {
|
||||
Route::get('/list/{estimateType}', ListController::class);
|
||||
Route::get('{estimateId}/delete', DeleteController::class);
|
||||
Route::post('/save-estimate', SaveController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Budget\Controllers\MainController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('budget')->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::prefix('/{costUnitId}')->group(function() {
|
||||
Route::get('/', MainController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import Modal from "../../../Views/Components/Modal.vue";
|
||||
import {reactive, ref} from "vue";
|
||||
import AmountInput from "../../../Views/Components/AmountInput.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const props = defineProps({
|
||||
showAddEstimate: Boolean,
|
||||
type: String,
|
||||
title: String,
|
||||
costUnitId: Number,
|
||||
amount: Number,
|
||||
amount_type: String,
|
||||
estimateId: Number,
|
||||
description: String,
|
||||
})
|
||||
|
||||
console.log(props)
|
||||
|
||||
const form = reactive({
|
||||
amount_type: props.amount_type,
|
||||
amount: props.amount,
|
||||
description: props.description,
|
||||
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const data = await request('/api/v1/budget/' + props.costUnitId + '/save-estimate', {
|
||||
method: "POST",
|
||||
body: {
|
||||
estimateId: props.estimateId,
|
||||
amount_type: form.amount_type,
|
||||
amount: form.amount,
|
||||
description: form.description,
|
||||
estimateType: props.type,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
|
||||
emit('closeAddEstimate')
|
||||
}
|
||||
|
||||
const emit = defineEmits(['closeAddEstimate'])
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:show="showAddEstimate"
|
||||
@close="emit('closeAddEstimate')"
|
||||
title="Ausgabenschätzung hinzufügen"
|
||||
width="600px"
|
||||
>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Kostenstelle</th>
|
||||
<td>{{title}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Verwendungszweck</th>
|
||||
<td><input type="text" v-model="form.description" style="width: 250px;" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Betrag</th>
|
||||
<td><AmountInput v-model="form.amount" style="width: 100px;" /> Euro</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Kostentyp</th>
|
||||
<td style="vertical-align: top;">
|
||||
<input type="radio" v-model="form.amount_type" value="flat"
|
||||
id="amount_type_flat" />
|
||||
<label for="amount_type_flat">Pauschal</label><br />
|
||||
|
||||
<input type="radio" v-model="form.amount_type" value="per_person" id="amount_type_per_person" />
|
||||
<label for="amount_type_per_person">Pro Person</label><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="button" value="Speichern" class="button" @click="save" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<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 ListBudgets from "./ListBudgetTypes.vue";
|
||||
|
||||
const props = defineProps({
|
||||
message: String,
|
||||
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
cost_unit_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 tabs = [
|
||||
{
|
||||
title: 'Verpflegung',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/catering",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Unterkunft',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/accommodation",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Programm',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/program",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Logistik',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/logistic",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Technik',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/technical",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Reisekosten',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/travelling",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Verwaltung',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/management",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Sonstiges',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/other",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Veranstaltungsbudget">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<script setup>
|
||||
import {createApp, ref} from 'vue'
|
||||
import LoadingModal from "../../../Views/Components/LoadingModal.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import {toast} from "vue3-toastify";
|
||||
import AddOrUpdateEstimate from "./AddOrUpdateEstimate.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
})
|
||||
|
||||
const localData = ref(props.data)
|
||||
|
||||
const showAddEstimate = ref(false)
|
||||
const estimateId = ref(null)
|
||||
const description = ref(null)
|
||||
const amount = ref(null)
|
||||
const amountType = ref(null)
|
||||
|
||||
const { data, loading, error, request, download } = useAjax()
|
||||
|
||||
async function reload() {
|
||||
const url = "/api/v1/budget/" + props.data.costUnitId + "/list/" + props.data.estimateType
|
||||
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 estimates:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function openAddEstimate() {
|
||||
estimateId.value = 0
|
||||
amount.value = 0.00
|
||||
amountType.value = 'flat'
|
||||
description.value = ''
|
||||
showAddEstimate.value = true
|
||||
}
|
||||
|
||||
async function openEditEstimate(localEstimateId, localDescription, localAmount, localAmountType, localEstimateType) {
|
||||
estimateId.value = localEstimateId
|
||||
description.value = localDescription
|
||||
amount.value = localAmount
|
||||
amountType.value = localAmountType
|
||||
console.log(localEstimateId, localDescription, localAmount, localAmountType, localEstimateType)
|
||||
console.log(estimateId.value, description.value, amount.value, amountType.value, localEstimateType)
|
||||
showAddEstimate.value = true
|
||||
}
|
||||
|
||||
async function deleteEstimate(currentEstimateId) {
|
||||
const data = await request('/api/v1/budget/' + props.data.costUnitId + '/' + currentEstimateId + '/delete', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
reload()
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="localData.estimates && localData.estimates.length > 0">
|
||||
<h2>{{ props.data.title }}</h2>
|
||||
<h3>Gesamtkosten: {{ localData.totalAmountString }}</h3>
|
||||
<span v-for="estimate in localData.estimates">
|
||||
<table style="width: 100%;">
|
||||
<tr><th style="width: 200px;">
|
||||
{{ estimate.title }}
|
||||
</th>
|
||||
<td>{{ estimate.singleAmountString }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style="padding-bottom: 30px">
|
||||
<label class="link" style="font-size: 10pt; margin-right: 20px;" @click="openEditEstimate(estimate.id, estimate.title, estimate.amountValue, estimate.amountType, props.data.estimateType)">Bearbeiten</label>
|
||||
<label class="link" style="font-size: 10pt; margin-right: 20px; color: #ff0000;" @click="deleteEstimate(estimate.id)">Löschen</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else>
|
||||
<strong style="width: 100%; text-align: center; display: block; margin-top: 20px;">
|
||||
Noch keine geschätzten Ausgaben vorhanden
|
||||
</strong>
|
||||
</div>
|
||||
<label class="link" @click="openAddEstimate()">
|
||||
Hinzufügen
|
||||
</label>
|
||||
<LoadingModal :show="showLoading" />
|
||||
<AddOrUpdateEstimate
|
||||
:amount="amount"
|
||||
:amount_type="amountType"
|
||||
:description="description"
|
||||
:estimateId="estimateId"
|
||||
:costUnitId="props.data.costUnitId"
|
||||
:title="props.data.title"
|
||||
:type="props.data.estimateType"
|
||||
:showAddEstimate="showAddEstimate"
|
||||
|
||||
v-if="showAddEstimate"
|
||||
@closeAddEstimate="showAddEstimate = false; reload()" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.costunit-list {
|
||||
width: 96% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
class ChangeCostUnitDetailsCommand {
|
||||
private ChangeCostUnitDetailsRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitDetailsRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute(): ChangeCostUnitDetailsResponse {
|
||||
$response = new ChangeCostUnitDetailsResponse();
|
||||
|
||||
$this->request->costUnit->distance_allowance = $this->request->distanceAllowance->getAmount();
|
||||
$this->request->costUnit->mail_on_new = $this->request->mailOnNew;
|
||||
$this->request->costUnit->billing_deadline = $this->request->billingDeadline;
|
||||
|
||||
$response->success = $this->request->costUnit->save();
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class ChangeCostUnitDetailsRequest {
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public Amount $distanceAllowance;
|
||||
public bool $mailOnNew;
|
||||
public ?DateTime $billingDeadline;
|
||||
|
||||
public function __construct(CostUnit $costUnit, Amount $distanceAllowance, bool $mailOnNew, ?DateTime $billingDeadline = null) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->distanceAllowance = $distanceAllowance;
|
||||
$this->mailOnNew = $mailOnNew;
|
||||
$this->billingDeadline = $billingDeadline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
class ChangeCostUnitDetailsResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
class ChangeCostUnitStateCommand {
|
||||
private ChangeCostUnitStateRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitStateRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeCostUnitStateResponse {
|
||||
$response = new ChangeCostUnitStateResponse();
|
||||
|
||||
$this->request->costUnit->allow_new = $this->request->allowNew;
|
||||
$this->request->costUnit->archived = $this->request->isArchived;
|
||||
$response->success = $this->request->costUnit->save();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class ChangeCostUnitStateRequest {
|
||||
public bool $allowNew;
|
||||
public bool $isArchived;
|
||||
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(CostUnit $costUnit, bool $allowNew, bool $isArchived) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->allowNew = $allowNew;
|
||||
$this->isArchived = $isArchived;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
class ChangeCostUnitStateResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class ChangeCostUnitTreasurersCommand {
|
||||
private ChangeCostUnitTreasurersRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitTreasurersRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeCostUnitTreasurersResponse {
|
||||
$response = new ChangeCostUnitTreasurersResponse();
|
||||
|
||||
try {
|
||||
$this->request->costUnit->resetTreasurers();
|
||||
|
||||
foreach ($this->request->treasurers as $treasurer) {
|
||||
$this->request->costUnit->treasurers()->attach($treasurer);
|
||||
}
|
||||
|
||||
$this->request->costUnit->save();
|
||||
} catch (\Throwable $th) {
|
||||
$response->success = false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class ChangeCostUnitTreasurersRequest {
|
||||
public array $treasurers;
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(CostUnit $costUnit, array $treasurers) {
|
||||
$this->treasurers = $treasurers;
|
||||
$this->costUnit = $costUnit;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
class ChangeCostUnitTreasurersResponse {
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user