Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b15573cda6 | ||
|
|
67c4e69f7b | ||
|
|
7f8417e915 | ||
|
|
f8dba18854 | ||
|
|
daff70b4fe | ||
|
|
7919d04dc3 | ||
|
|
cea00f9679 | ||
|
|
a0d26648ee | ||
|
|
e55cdd1988 | ||
|
|
d1ef092bc3 | ||
|
|
78f4d813f9 | ||
|
|
8d6a4041c1 | ||
|
|
29db141b84 | ||
|
|
ab3d526217 | ||
|
|
6c9281516e | ||
|
|
afc91545a9 | ||
|
|
12d98b4d7e | ||
|
|
6c7fe56579 | ||
|
|
e09987f5a8 | ||
|
|
5c514e9ff5 | ||
|
|
bc60461dac | ||
|
|
6848fbd95f | ||
|
|
012ebb6538 | ||
|
|
c1ef1d71ad | ||
|
|
aebb2f9aaa | ||
|
|
cfc7c7eee2 | ||
|
|
1b9384dad1 | ||
|
|
fed54514c8 | ||
|
|
12f05ceb09 | ||
|
|
5f56ef94a6 | ||
|
|
79bb186234 | ||
|
|
a012c16425 | ||
|
|
a8205a4f96 | ||
|
|
dbcebbb2c4 | ||
|
|
63c7b8dfb1 | ||
|
|
e9aa66a860 | ||
|
|
51c4055c47 | ||
|
|
2348663fd8 | ||
|
|
a83cec94ab | ||
|
|
710e27c344 | ||
|
|
83bbd6f7d3 |
@@ -0,0 +1,17 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
Laravel 12 + Inertia/Vue, mandantenfähig (custom `tenant`-Scoping über `app('tenant')`, gesetzt in
|
||||||
|
`app/Middleware/IdentifyTenant.php`). PHP 8.5 — Tests/Artisan laufen im Container:
|
||||||
|
`docker exec mareike-mareike-app-1 php artisan test`.
|
||||||
|
|
||||||
|
## Projektkonventionen (verbindlich)
|
||||||
|
|
||||||
|
Die verbindlichen Konventionen (Actions/Request-Command-Response, Controller, Repositories, Models/Resources, Tenant,
|
||||||
|
Routing, Mails) stehen in `.ai/conventions.md` und werden hier eingebunden — **immer beachten**:
|
||||||
|
|
||||||
|
@.ai/conventions.md
|
||||||
|
|
||||||
|
## Bereichs-Dokumentation
|
||||||
|
|
||||||
|
- [Zahlungsmodule](app/EventPaymentModules/CLAUDE.md) — interface-gesteuerte Zahlungsarten (Optionen, Config,
|
||||||
|
Anmelde-Zusammenfassung, GiroCode/Fähigkeits-Interfaces, neues Modul hinzufügen).
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||||
|
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
use App\Models\PaymentMethod;
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$paymentMethodDefaults = PaymentMethod::defaults();
|
||||||
|
foreach (PaymentMethod::all() as $paymentMethod) {
|
||||||
|
$defaults = $paymentMethodDefaults[$paymentMethod->slug] ?? ['name' => $paymentMethod->slug, 'description' => null, 'configuration' => []];
|
||||||
|
|
||||||
|
AvailablePaymentMethod::create([
|
||||||
|
'tenant' => $tenant->slug,
|
||||||
|
'slug' => $paymentMethod->slug,
|
||||||
|
'name' => $defaults['name'],
|
||||||
|
'description' => $defaults['description'],
|
||||||
|
'active' => true,
|
||||||
|
'configuration' => PaymentMethod::sanitizeConfiguration($paymentMethod->slug, $defaults['configuration'] ?? []),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\RemovePaymentMethodUsage;
|
||||||
|
|
||||||
|
use App\Mail\EventReportMails\PaymentMethodsExhaustedMail;
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
|
class RemovePaymentMethodUsageAction
|
||||||
|
{
|
||||||
|
public function execute(AvailablePaymentMethod $availablePaymentMethod): void
|
||||||
|
{
|
||||||
|
foreach ($availablePaymentMethod->events()->get() as $event) {
|
||||||
|
$event->paymentMethods()->detach($availablePaymentMethod->slug);
|
||||||
|
|
||||||
|
$remainingActive = $event->paymentMethods()->where('active', true)->count();
|
||||||
|
if ($remainingActive === 0 && $event->registration_allowed) {
|
||||||
|
$event->registration_allowed = false;
|
||||||
|
$event->save();
|
||||||
|
|
||||||
|
$recipients = $event->eventManagers;
|
||||||
|
if ($event->costUnit !== null) {
|
||||||
|
$recipients = $recipients->merge($event->costUnit->treasurers);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($recipients->unique('id') as $recipient) {
|
||||||
|
Mail::to($recipient->email)->send(new PaymentMethodsExhaustedMail($event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateAvailablePaymentMethod;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\RemovePaymentMethodUsage\RemovePaymentMethodUsageAction;
|
||||||
|
use App\Models\PaymentMethod;
|
||||||
|
|
||||||
|
class UpdateAvailablePaymentMethodAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateAvailablePaymentMethodRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateAvailablePaymentMethodResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateAvailablePaymentMethodResponse();
|
||||||
|
$paymentMethod = $this->request->availablePaymentMethod;
|
||||||
|
$wasActive = $paymentMethod->active;
|
||||||
|
|
||||||
|
// Nur bekannte Options-Keys übernehmen -- das Options-Schema des Zahlungsmoduls ist die Autorität.
|
||||||
|
$configuration = PaymentMethod::sanitizeConfiguration($paymentMethod->slug, $this->request->configuration);
|
||||||
|
|
||||||
|
// Guard: Aktivierung nur erlaubt, wenn alle Pflicht-Optionen befüllt sind.
|
||||||
|
if ($this->request->active && !PaymentMethod::isConfigurationComplete($paymentMethod->slug, $configuration)) {
|
||||||
|
$response->success = false;
|
||||||
|
$response->message = 'Bitte zuerst alle Pflichtfelder ausfüllen, bevor die Zahlungsmethode aktiviert wird.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentMethod->update([
|
||||||
|
'name' => $this->request->name,
|
||||||
|
'description' => $this->request->description,
|
||||||
|
'active' => $this->request->active,
|
||||||
|
'configuration' => $configuration,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($wasActive && !$this->request->active) {
|
||||||
|
(new RemovePaymentMethodUsageAction())->execute($paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Zahlungsmethode wurde gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateAvailablePaymentMethod;
|
||||||
|
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
|
||||||
|
class UpdateAvailablePaymentMethodRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $configuration
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public AvailablePaymentMethod $availablePaymentMethod,
|
||||||
|
public string $name,
|
||||||
|
public ?string $description,
|
||||||
|
public bool $active,
|
||||||
|
public array $configuration = [],
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateAvailablePaymentMethod;
|
||||||
|
|
||||||
|
class UpdateAvailablePaymentMethodResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -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,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||||
|
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
use App\Models\PaymentMethod;
|
||||||
|
use App\Scopes\SiteScope;
|
||||||
|
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->syncTransferPaymentConfiguration();
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'IBAN-Informationen wurden gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Übernimmt die eingegebenen IBAN-Daten direkt in die Tenant-Config der Überweisungs-Zahlungsart
|
||||||
|
* (Kontoinhaber/IBAN/BIC), damit sie nicht doppelt gepflegt werden müssen. Bestehende weitere
|
||||||
|
* Config-Werte (z.B. Symbol) bleiben erhalten. Der Ziel-Tenant kann ein verwalteter sein, daher
|
||||||
|
* ohne SiteScope explizit auf den Tenant-Slug gefiltert.
|
||||||
|
*/
|
||||||
|
private function syncTransferPaymentConfiguration(): void
|
||||||
|
{
|
||||||
|
$slug = PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION;
|
||||||
|
|
||||||
|
$method = AvailablePaymentMethod::withoutGlobalScope(SiteScope::class)
|
||||||
|
->where('tenant', $this->request->tenant->slug)
|
||||||
|
->where('slug', $slug)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($method === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$method->configuration = PaymentMethod::sanitizeConfiguration($slug, array_merge(
|
||||||
|
$method->configuration ?? [],
|
||||||
|
[
|
||||||
|
'account_owner' => $this->request->accountName,
|
||||||
|
'iban' => $this->request->accountIban,
|
||||||
|
'bic' => $this->request->accountBic,
|
||||||
|
],
|
||||||
|
));
|
||||||
|
$method->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
use App\Resources\AvailablePaymentMethodResource;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantPaymentMethodsGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'paymentMethods' => AvailablePaymentMethod::all()
|
||||||
|
->map(fn (AvailablePaymentMethod $method) => new AvailablePaymentMethodResource($method)->toArray($request)),
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenant/payment-methods',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateAvailablePaymentMethod\UpdateAvailablePaymentMethodAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateAvailablePaymentMethod\UpdateAvailablePaymentMethodRequest;
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantPaymentMethodsUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$availablePaymentMethod = AvailablePaymentMethod::findOrFail($id);
|
||||||
|
|
||||||
|
$action = new UpdateAvailablePaymentMethodAction(new UpdateAvailablePaymentMethodRequest(
|
||||||
|
availablePaymentMethod: $availablePaymentMethod,
|
||||||
|
name: $request->input('name'),
|
||||||
|
description: $request->input('description'),
|
||||||
|
active: (bool) $request->input('active'),
|
||||||
|
configuration: (array) $request->input('configuration', []),
|
||||||
|
));
|
||||||
|
|
||||||
|
$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\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,75 @@
|
|||||||
|
<?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\Domains\Admin\Controllers\TenantPaymentMethodsGetController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantPaymentMethodsUpdateController;
|
||||||
|
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('/payment-methods', TenantPaymentMethodsGetController::class);
|
||||||
|
Route::post('/payment-methods/{id}', TenantPaymentMethodsUpdateController::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,264 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed, ref} from 'vue';
|
||||||
|
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
||||||
|
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||||
|
import RichSelectBox from "../../../../Views/Components/RichSelectBox.vue";
|
||||||
|
import {PAYMENT_METHOD_ICONS} from "../../../../../resources/js/constants/paymentMethodIcons.js";
|
||||||
|
import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const paymentMethods = ref(props.data.paymentMethods ?? [])
|
||||||
|
const editing = ref(null)
|
||||||
|
const form = ref({ name: '', description: '', active: true, configuration: {} })
|
||||||
|
|
||||||
|
// Sind alle Pflichtfelder des aktuell bearbeiteten Moduls befüllt? Der Server ist die
|
||||||
|
// eigentliche Autorität (Guard), das hier dient nur der Bedienführung.
|
||||||
|
const requiredComplete = computed(() => {
|
||||||
|
if (!editing.value) return true
|
||||||
|
return (editing.value.optionsSchema ?? [])
|
||||||
|
.filter(option => option.required)
|
||||||
|
.every(option => {
|
||||||
|
const value = form.value.configuration[option.name]
|
||||||
|
return value !== undefined && value !== null && value !== ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function edit(paymentMethod) {
|
||||||
|
editing.value = paymentMethod
|
||||||
|
const configuration = {}
|
||||||
|
for (const option of paymentMethod.optionsSchema ?? []) {
|
||||||
|
configuration[option.name] = paymentMethod.configuration?.[option.name] ?? ''
|
||||||
|
}
|
||||||
|
form.value = {
|
||||||
|
name: paymentMethod.name,
|
||||||
|
description: paymentMethod.description ?? '',
|
||||||
|
active: paymentMethod.active,
|
||||||
|
configuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
editing.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const response = await request('/api/v1/admin/tenant/payment-methods/' + editing.value.id, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
Object.assign(editing.value, form.value)
|
||||||
|
close()
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<table class="payment-method-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th class="empty"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="paymentMethod in paymentMethods" :key="paymentMethod.id">
|
||||||
|
<td>{{ paymentMethod.name }}</td>
|
||||||
|
<td>
|
||||||
|
<span :class="paymentMethod.active ? 'badge-active' : 'badge-inactive'">
|
||||||
|
{{ paymentMethod.active ? 'Aktiv' : 'Inaktiv' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-edit" @click="edit(paymentMethod)">Bearbeiten</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<FullScreenModal :show="editing !== null" @close="close">
|
||||||
|
<template v-if="editing">
|
||||||
|
<h2>Zahlungsmodul bearbeiten</h2>
|
||||||
|
<table class="data-table">
|
||||||
|
<tr><th>Name</th><td><input type="text" v-model="form.name" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Beschreibung</th><td><textarea v-model="form.description" class="form-input"></textarea></td></tr>
|
||||||
|
<tr v-for="option in editing.optionsSchema ?? []" :key="option.name">
|
||||||
|
<th>{{ option.label }}<span v-if="option.required" class="required-marker">*</span></th>
|
||||||
|
<td>
|
||||||
|
<RichSelectBox v-if="option.type === 'icon'" v-model="form.configuration[option.name]"
|
||||||
|
:options="PAYMENT_METHOD_ICONS" placeholder="Symbol wählen…"/>
|
||||||
|
<TextEditor v-else-if="option.type === 'richtext'" v-model="form.configuration[option.name]"/>
|
||||||
|
<input v-else type="text" v-model="form.configuration[option.name]" class="form-input"/>
|
||||||
|
<span v-if="option.hint" class="option-hint">{{ option.hint }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Aktiv</th>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" v-model="form.active" :disabled="!requiredComplete && !form.active" />
|
||||||
|
<span v-if="!requiredComplete" class="hint">Bitte zuerst alle Pflichtfelder (*) ausfüllen, um zu aktivieren.</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
<button class="btn-cancel" @click="close">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FullScreenModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.payment-method-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method-table thead th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method-table tbody td {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required-marker {
|
||||||
|
color: #ef4444;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,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,115 @@
|
|||||||
|
<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";
|
||||||
|
import TenantPaymentMethods from "./Partials/TenantPaymentMethods.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tenant: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: 'Kontaktdaten',
|
||||||
|
component: TenantContact,
|
||||||
|
endpoint: '/api/v1/admin/tenant/contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IBAN-Informationen',
|
||||||
|
component: TenantPayment,
|
||||||
|
endpoint: '/api/v1/admin/tenant/payment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Zahlungsmodule',
|
||||||
|
component: TenantPaymentMethods,
|
||||||
|
endpoint: '/api/v1/admin/tenant/payment-methods',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: 'IBAN-Informationen',
|
||||||
|
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>
|
||||||
@@ -4,14 +4,13 @@ namespace App\Domains\CostUnit\Controllers;
|
|||||||
|
|
||||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
|
||||||
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||||
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Models\SepaPaymentElement;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Providers\FileWriteProvider;
|
use App\Providers\FileWriteProvider;
|
||||||
use App\Providers\InvoiceCsvFileProvider;
|
use App\Providers\InvoiceCsvFileProvider;
|
||||||
use App\Providers\PainFileProvider;
|
|
||||||
use App\Providers\WebDavProvider;
|
use App\Providers\WebDavProvider;
|
||||||
use App\Providers\ZipArchiveFileProvider;
|
use App\Providers\ZipArchiveFileProvider;
|
||||||
use App\Scopes\CommonController;
|
use App\Scopes\CommonController;
|
||||||
@@ -25,24 +24,19 @@ class ExportController extends CommonController {
|
|||||||
|
|
||||||
$webdavProvider = new WebDavProvider(WebDavProvider::INVOICE_PREFIX . $this->tenant->url . '/' . $costUnit->name);
|
$webdavProvider = new WebDavProvider(WebDavProvider::INVOICE_PREFIX . $this->tenant->url . '/' . $costUnit->name);
|
||||||
|
|
||||||
$painFileData = $this->painData($invoicesForExport);
|
$this->createSepaPaymentElements($invoicesForExport, $costUnit);
|
||||||
$csvData = $this->csvData($invoicesForExport);
|
$csvData = $this->csvData($invoicesForExport);
|
||||||
|
|
||||||
$filePrefix = Tenant::getTempDirectory();
|
$filePrefix = Tenant::getTempDirectory();
|
||||||
|
|
||||||
$painFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '-sepa.xml', $painFileData);
|
|
||||||
$painFileWriteProvider->writeToFile();
|
|
||||||
|
|
||||||
$csvFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '.csv', $csvData);
|
$csvFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '.csv', $csvData);
|
||||||
$csvFileWriteProvider->writeToFile();
|
$csvFileWriteProvider->writeToFile();
|
||||||
|
|
||||||
if ($this->tenant->upload_exports) {
|
if ($this->tenant->upload_exports) {
|
||||||
$webdavProvider->uploadFile($painFileWriteProvider->fileName);
|
|
||||||
$webdavProvider->uploadFile($csvFileWriteProvider->fileName);
|
$webdavProvider->uploadFile($csvFileWriteProvider->fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
$downloadZipArchiveFiles = [
|
$downloadZipArchiveFiles = [
|
||||||
$painFileWriteProvider->fileName,
|
|
||||||
$csvFileWriteProvider->fileName
|
$csvFileWriteProvider->fileName
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -72,7 +66,6 @@ class ExportController extends CommonController {
|
|||||||
Storage::delete($file);
|
Storage::delete($file);
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::delete($painFileWriteProvider->fileName);
|
|
||||||
Storage::delete($csvFileWriteProvider->fileName);
|
Storage::delete($csvFileWriteProvider->fileName);
|
||||||
|
|
||||||
return response()->download(
|
return response()->download(
|
||||||
@@ -82,24 +75,28 @@ class ExportController extends CommonController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::delete($painFileWriteProvider->fileName);
|
|
||||||
Storage::delete($csvFileWriteProvider->fileName);
|
Storage::delete($csvFileWriteProvider->fileName);
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Die Abrechnungen wurden exportiert.' . PHP_EOL .'Die Belege werden asynchron auf dem Webdav-Server hinterlegt.' . PHP_EOL . PHP_EOL . 'Sollten diese in 15 Minuten nicht vollständig sein, kontaktiere den Adminbistrator.'
|
'message' => 'Die Abrechnungen wurden exportiert.' . PHP_EOL . 'Die SEPA-Überweisungsdatei kann über den Tab "Globale Aktionen" in der Kostenstellenübersicht erzeugt werden.' . PHP_EOL . PHP_EOL . 'Die Belege werden asynchron auf dem Webdav-Server hinterlegt.' . PHP_EOL . 'Sollten diese in 15 Minuten nicht vollständig sein, kontaktiere den Administrator.'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function painData(array $invoices) : string {
|
private function createSepaPaymentElements(array $invoices, $costUnit): void
|
||||||
$invoicesForPainFile = [];
|
{
|
||||||
foreach ($invoices as $invoice) {
|
foreach ($invoices as $invoice) {
|
||||||
if ($invoice->contact_bank_owner !== null && $invoice->contact_bank_iban !== '' && !$invoice->donation) {
|
if ($invoice->contact_bank_owner !== null && $invoice->contact_bank_iban !== '' && !$invoice->donation) {
|
||||||
$invoicesForPainFile[] = $invoice;
|
SepaPaymentElement::create([
|
||||||
|
'tenant' => $this->tenant->slug,
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'cost_unit_id' => $costUnit->id,
|
||||||
|
'amount' => $invoice->amount,
|
||||||
|
'recipient_name' => $invoice->contact_bank_owner,
|
||||||
|
'recipient_iban' => $invoice->contact_bank_iban,
|
||||||
|
'payment_purpose' => $invoice->payment_purpose ?? 'Auslagenerstattung Rechnungsnummer ' . $invoice->invoice_number,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$painFileProvider = new PainFileProvider($this->tenant->account_iban, $this->tenant->account_name, $this->tenant->account_bic, $invoicesForPainFile);
|
|
||||||
return $painFileProvider->createPainFileContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function csvData(array $invoices) : string {
|
public function csvData(array $invoices) : string {
|
||||||
@@ -107,4 +104,3 @@ class ExportController extends CommonController {
|
|||||||
return $csvDateProvider->createCsvFileContent();
|
return $csvDateProvider->createCsvFileContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\CostUnit\Controllers;
|
||||||
|
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\SepaPaymentElement;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Providers\AuthCheckProvider;
|
||||||
|
use App\Providers\FileWriteProvider;
|
||||||
|
use App\Providers\PainFileProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class GlobalSepaExportController extends CommonController {
|
||||||
|
|
||||||
|
private function checkAuthorization(): void
|
||||||
|
{
|
||||||
|
$authCheck = new AuthCheckProvider();
|
||||||
|
$role = $authCheck->getUserRole();
|
||||||
|
if (!in_array($role, [UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER], true)) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGlobalActions()
|
||||||
|
{
|
||||||
|
$this->checkAuthorization();
|
||||||
|
|
||||||
|
$pendingElements = SepaPaymentElement::where('exported', false)->get();
|
||||||
|
$pendingCount = $pendingElements->count();
|
||||||
|
$pendingAmount = number_format($pendingElements->sum('amount'), 2, ',', '.');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'pending_count' => $pendingCount,
|
||||||
|
'pending_amount' => $pendingAmount,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportSepaFile()
|
||||||
|
{
|
||||||
|
$this->checkAuthorization();
|
||||||
|
|
||||||
|
return DB::transaction(function () {
|
||||||
|
$elements = SepaPaymentElement::where('exported', false)->lockForUpdate()->get();
|
||||||
|
|
||||||
|
if ($elements->isEmpty()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Es gibt keine ausstehenden SEPA-Überweisungen.'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$painFileProvider = new PainFileProvider(
|
||||||
|
$this->tenant->account_iban,
|
||||||
|
$this->tenant->account_name,
|
||||||
|
$this->tenant->account_bic,
|
||||||
|
$elements->all()
|
||||||
|
);
|
||||||
|
|
||||||
|
$painContent = $painFileProvider->createPainFileContent();
|
||||||
|
|
||||||
|
$filePrefix = Tenant::getTempDirectory();
|
||||||
|
$fileName = $filePrefix . 'sepa-pain-' . date('Y-m-d_H-i') . '.xml';
|
||||||
|
|
||||||
|
$fileWriteProvider = new FileWriteProvider($fileName, $painContent);
|
||||||
|
$fileWriteProvider->writeToFile();
|
||||||
|
|
||||||
|
$elements->each(function (SepaPaymentElement $element) {
|
||||||
|
$element->update([
|
||||||
|
'exported' => true,
|
||||||
|
'exported_at' => now(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$filePath = storage_path('app/private/' . $fileName);
|
||||||
|
|
||||||
|
return response()->download($filePath, basename($fileName), [
|
||||||
|
'Content-Type' => 'application/xml',
|
||||||
|
])->deleteFileAfterSend(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ use App\Domains\CostUnit\Controllers\CreateController;
|
|||||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||||
use App\Domains\CostUnit\Controllers\EditController;
|
use App\Domains\CostUnit\Controllers\EditController;
|
||||||
use App\Domains\CostUnit\Controllers\ExportController;
|
use App\Domains\CostUnit\Controllers\ExportController;
|
||||||
|
use App\Domains\CostUnit\Controllers\GlobalSepaExportController;
|
||||||
use App\Domains\CostUnit\Controllers\ListController;
|
use App\Domains\CostUnit\Controllers\ListController;
|
||||||
use App\Domains\CostUnit\Controllers\OpenController;
|
use App\Domains\CostUnit\Controllers\OpenController;
|
||||||
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
||||||
@@ -43,6 +44,9 @@ Route::prefix('api/v1')
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/global-actions', [GlobalSepaExportController::class, 'getGlobalActions']);
|
||||||
|
Route::get('/export-sepa-file', [GlobalSepaExportController::class, 'exportSepaFile']);
|
||||||
|
|
||||||
Route::prefix('open')->group(function () {
|
Route::prefix('open')->group(function () {
|
||||||
Route::get('/current-events', [ListController::class, 'listCurrentEvents']);
|
Route::get('/current-events', [ListController::class, 'listCurrentEvents']);
|
||||||
Route::get('/current-running-jobs', [ListController::class, 'listCurrentRunningJobs']);
|
Route::get('/current-running-jobs', [ListController::class, 'listCurrentRunningJobs']);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
|||||||
import {toast} from "vue3-toastify";
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
import ListCostUnits from "./Partials/ListCostUnits.vue";
|
import ListCostUnits from "./Partials/ListCostUnits.vue";
|
||||||
|
import GlobalActions from "./Partials/GlobalActions.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
message: String,
|
message: String,
|
||||||
@@ -63,6 +64,13 @@ const tabs = [
|
|||||||
deep_jump_id: initialCostUnitId,
|
deep_jump_id: initialCostUnitId,
|
||||||
deep_jump_id_sub: initialInvoiceId,
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Globale Aktionen',
|
||||||
|
component: GlobalActions,
|
||||||
|
endpoint: "/api/v1/cost-unit/global-actions",
|
||||||
|
deep_jump_id: 0,
|
||||||
|
deep_jump_id_sub: 0,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import LoadingModal from "../../../../Views/Components/LoadingModal.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: [Array, Object],
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
deep_jump_id: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
deep_jump_id_sub: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const showLoading = ref(false)
|
||||||
|
|
||||||
|
async function exportSepaFile() {
|
||||||
|
showLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/cost-unit/export-sepa-file', {
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
const data = await response.json();
|
||||||
|
toast.info(data.message);
|
||||||
|
} else {
|
||||||
|
throw new Error('Fehler beim Erzeugen der SEPA-Datei');
|
||||||
|
}
|
||||||
|
showLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = "sepa-pain-" + new Date().toISOString().slice(0, 10) + ".xml";
|
||||||
|
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
toast.success('SEPA-Datei wurde erfolgreich erzeugt.');
|
||||||
|
showLoading.value = false;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
showLoading.value = false;
|
||||||
|
toast.error('Beim Erzeugen der SEPA-Datei ist ein Fehler aufgetreten.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2>Globale Aktionen</h2>
|
||||||
|
|
||||||
|
<div style="margin: 20px 0;">
|
||||||
|
<p v-if="props.data.pending_count > 0">
|
||||||
|
Es gibt <strong>{{ props.data.pending_count }}</strong> ausstehende SEPA-Überweisungen
|
||||||
|
(Gesamtbetrag: <strong>{{ props.data.pending_amount }} Euro</strong>).
|
||||||
|
</p>
|
||||||
|
<p v-else>
|
||||||
|
Keine ausstehenden SEPA-Überweisungen vorhanden.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="action-button"
|
||||||
|
:disabled="!props.data.pending_count || props.data.pending_count === 0"
|
||||||
|
@click="exportSepaFile"
|
||||||
|
>
|
||||||
|
Erzeuge SEPA-File
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<loading-modal v-if="showLoading" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.action-button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #0073aa;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover:not(:disabled) {
|
||||||
|
background-color: #005a87;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,10 +3,12 @@
|
|||||||
namespace App\Domains\Event\Actions\CreateEvent;
|
namespace App\Domains\Event\Actions\CreateEvent;
|
||||||
|
|
||||||
use App\Enumerations\EatingHabit;
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
use App\Models\Event;
|
use App\Models\Event;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\RelationModels\EventEatingHabits;
|
use App\RelationModels\EventEatingHabits;
|
||||||
use App\RelationModels\EventLocalGroups;
|
use App\RelationModels\EventLocalGroups;
|
||||||
|
use App\RelationModels\EventPaymentMethods;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class CreateEventCommand {
|
class CreateEventCommand {
|
||||||
@@ -41,7 +43,7 @@ class CreateEventCommand {
|
|||||||
'account_iban' => $this->request->accountIban,
|
'account_iban' => $this->request->accountIban,
|
||||||
'participation_fee_type' => $this->request->participationFeeType->slug,
|
'participation_fee_type' => $this->request->participationFeeType->slug,
|
||||||
'pay_per_day' => $this->request->payPerDay,
|
'pay_per_day' => $this->request->payPerDay,
|
||||||
'pay_direct' => $this->request->payDirect,
|
'pay_direct' => false,
|
||||||
'total_max_amount' => 0,
|
'total_max_amount' => 0,
|
||||||
'support_per_person' => 0,
|
'support_per_person' => 0,
|
||||||
'support_flat' => 0,
|
'support_flat' => 0,
|
||||||
@@ -58,6 +60,14 @@ class CreateEventCommand {
|
|||||||
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGETARIAN)->first()->id,
|
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGETARIAN)->first()->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
foreach (AvailablePaymentMethod::where('active', true)->get() as $availablePaymentMethod) {
|
||||||
|
EventPaymentMethods::create([
|
||||||
|
'event_id' => $event->id,
|
||||||
|
'slug' => $availablePaymentMethod->slug,
|
||||||
|
'configuration' => $availablePaymentMethod->configuration ?? [],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (app('tenant')->slug === 'lv') {
|
if (app('tenant')->slug === 'lv') {
|
||||||
foreach(Tenant::where(['is_active_local_group' => true])->get() as $tenant) {
|
foreach(Tenant::where(['is_active_local_group' => true])->get() as $tenant) {
|
||||||
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => $tenant->id]);
|
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => $tenant->id]);
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ class CreateEventRequest {
|
|||||||
public string $accountOwner;
|
public string $accountOwner;
|
||||||
public string $accountIban;
|
public string $accountIban;
|
||||||
public bool $payPerDay;
|
public bool $payPerDay;
|
||||||
public bool $payDirect;
|
|
||||||
|
|
||||||
public function __construct(string $name, string $location, string $postalCode, string $email, DateTime $begin, DateTime $end, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $earlyBirdEndAmountIncrease, ParticipationFeeType $participationFeeType, string $accountOwner, string $accountIban, bool $payPerDay, bool $payDirect) {
|
public function __construct(string $name, string $location, string $postalCode, string $email, DateTime $begin, DateTime $end, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $earlyBirdEndAmountIncrease, ParticipationFeeType $participationFeeType, string $accountOwner, string $accountIban, bool $payPerDay) {
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->location = $location;
|
$this->location = $location;
|
||||||
$this->postalCode = $postalCode;
|
$this->postalCode = $postalCode;
|
||||||
@@ -35,6 +34,5 @@ class CreateEventRequest {
|
|||||||
$this->accountOwner = $accountOwner;
|
$this->accountOwner = $accountOwner;
|
||||||
$this->accountIban = $accountIban;
|
$this->accountIban = $accountIban;
|
||||||
$this->payPerDay = $payPerDay;
|
$this->payPerDay = $payPerDay;
|
||||||
$this->payDirect = $payDirect;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetPaymentMethods;
|
||||||
|
|
||||||
|
use App\Models\AvailablePaymentMethod;
|
||||||
|
use App\Models\PaymentMethod;
|
||||||
|
use App\RelationModels\EventPaymentMethods;
|
||||||
|
|
||||||
|
class SetPaymentMethodsCommand
|
||||||
|
{
|
||||||
|
public SetPaymentMethodsRequest $request;
|
||||||
|
|
||||||
|
public function __construct(SetPaymentMethodsRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): SetPaymentMethodsResponse
|
||||||
|
{
|
||||||
|
$response = new SetPaymentMethodsResponse();
|
||||||
|
|
||||||
|
if (count($this->request->paymentMethods) === 0) {
|
||||||
|
$response->success = false;
|
||||||
|
$response->message = 'Mindestens eine Zahlungsmöglichkeit muss ausgewählt sein.';
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->syncPaymentMethods();
|
||||||
|
|
||||||
|
$this->request->event->save();
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Differenzieller Sync der Zahlungsmethoden mit Snapshot-Copy-Semantik:
|
||||||
|
* - entfernte Methoden werden gelöscht,
|
||||||
|
* - neue Methoden erhalten einen Snapshot der Tenant-Config (oder eines expliziten Overrides),
|
||||||
|
* - bestehende Methoden behalten ihre pro-Event-Config, sofern kein Override übergeben wird.
|
||||||
|
*/
|
||||||
|
private function syncPaymentMethods(): void
|
||||||
|
{
|
||||||
|
$event = $this->request->event;
|
||||||
|
$desiredSlugs = $this->request->paymentMethods;
|
||||||
|
$overrides = $this->request->paymentMethodConfigurations;
|
||||||
|
|
||||||
|
$existing = EventPaymentMethods::where('event_id', $event->id)->get()->keyBy('slug');
|
||||||
|
|
||||||
|
// Nicht mehr gewünschte Methoden entfernen.
|
||||||
|
foreach ($existing as $slug => $row) {
|
||||||
|
if (!in_array($slug, $desiredSlugs, true)) {
|
||||||
|
$row->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tenant-Instanzen (für Snapshot-Kopie neuer Methoden) laden.
|
||||||
|
$tenantMethods = AvailablePaymentMethod::whereIn('slug', $desiredSlugs)->get()->keyBy('slug');
|
||||||
|
|
||||||
|
foreach ($desiredSlugs as $slug) {
|
||||||
|
$override = $overrides[$slug] ?? null;
|
||||||
|
|
||||||
|
if (isset($existing[$slug])) {
|
||||||
|
// Bestehende Zuweisung: Config nur bei explizitem Override anpassen, sonst unverändert lassen.
|
||||||
|
if ($override !== null) {
|
||||||
|
$row = $existing[$slug];
|
||||||
|
$row->configuration = PaymentMethod::sanitizeConfiguration($slug, $override);
|
||||||
|
$row->save();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Zuweisung: expliziter Override oder Snapshot der Tenant-Config.
|
||||||
|
$snapshot = $override ?? ($tenantMethods[$slug]->configuration ?? []);
|
||||||
|
EventPaymentMethods::create([
|
||||||
|
'event_id' => $event->id,
|
||||||
|
'slug' => $slug,
|
||||||
|
'configuration' => PaymentMethod::sanitizeConfiguration($slug, $snapshot ?? []),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetPaymentMethods;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
|
||||||
|
class SetPaymentMethodsRequest
|
||||||
|
{
|
||||||
|
public Event $event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gewünschte Zahlungsmethoden-Slugs für das Event.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
public array $paymentMethods;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionale pro-Event-Config-Overrides je Zahlungsmethode: [slug => [option => value]].
|
||||||
|
* Fehlt ein Slug, bleibt die bestehende Config erhalten bzw. wird beim ersten Zuweisen
|
||||||
|
* aus der Tenant-Config als Snapshot kopiert.
|
||||||
|
*
|
||||||
|
* @var array<string, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public array $paymentMethodConfigurations;
|
||||||
|
|
||||||
|
public function __construct(Event $event, array $paymentMethods, array $paymentMethodConfigurations = [])
|
||||||
|
{
|
||||||
|
$this->event = $event;
|
||||||
|
$this->paymentMethods = $paymentMethods;
|
||||||
|
$this->paymentMethodConfigurations = $paymentMethodConfigurations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetPaymentMethods;
|
||||||
|
|
||||||
|
class SetPaymentMethodsResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Event\Actions\SignUp;
|
|||||||
|
|
||||||
use App\Enumerations\EatingHabit;
|
use App\Enumerations\EatingHabit;
|
||||||
use App\Enumerations\EfzStatus;
|
use App\Enumerations\EfzStatus;
|
||||||
|
use App\EventPaymentModules\EventPaymentModuleRegistry;
|
||||||
use App\ValueObjects\Age;
|
use App\ValueObjects\Age;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
@@ -14,6 +15,18 @@ class SignUpCommand {
|
|||||||
public function execute() : SignUpResponse {
|
public function execute() : SignUpResponse {
|
||||||
$response = new SignUpResponse();
|
$response = new SignUpResponse();
|
||||||
|
|
||||||
|
// Teilnehmer-Eingaben der gewählten Zahlungsart gegen das Modul-Schema absichern.
|
||||||
|
$module = $this->request->paymentMethod !== null
|
||||||
|
? EventPaymentModuleRegistry::forSlug($this->request->paymentMethod)
|
||||||
|
: null;
|
||||||
|
$paymentOptions = $module?->sanitizeParticipantOptions($this->request->paymentOptions) ?? [];
|
||||||
|
|
||||||
|
if ($module !== null && !$module->participantOptionsComplete($paymentOptions)) {
|
||||||
|
$response->success = false;
|
||||||
|
$response->message = 'Bitte alle erforderlichen Zahlungsangaben ausfüllen.';
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
$eatingHabit = match ($this->request->eating_habit) {
|
$eatingHabit = match ($this->request->eating_habit) {
|
||||||
'vegan' => EatingHabit::EATING_HABIT_VEGAN,
|
'vegan' => EatingHabit::EATING_HABIT_VEGAN,
|
||||||
'vegetarian' => EatingHabit::EATING_HABIT_VEGETARIAN,
|
'vegetarian' => EatingHabit::EATING_HABIT_VEGETARIAN,
|
||||||
@@ -21,6 +34,7 @@ class SignUpCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$participantAge = new Age($this->request->birthday);
|
$participantAge = new Age($this->request->birthday);
|
||||||
|
|
||||||
$response->participant = $this->request->event->participants()->create(
|
$response->participant = $this->request->event->participants()->create(
|
||||||
[
|
[
|
||||||
'tenant' => $this->request->event->tenant,
|
'tenant' => $this->request->event->tenant,
|
||||||
@@ -60,6 +74,8 @@ class SignUpCommand {
|
|||||||
'notes' => $this->request->notes,
|
'notes' => $this->request->notes,
|
||||||
'amount' => $this->request->amount,
|
'amount' => $this->request->amount,
|
||||||
'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname,
|
'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname,
|
||||||
|
'payment_method' => $this->request->paymentMethod,
|
||||||
|
'payment_options' => $paymentOptions,
|
||||||
'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Domains\Event\Actions\SignUp;
|
namespace App\Domains\Event\Actions\SignUp;
|
||||||
|
|
||||||
use App\Enumerations\ParticipationType;
|
|
||||||
use App\Models\Event;
|
use App\Models\Event;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
@@ -44,7 +43,9 @@ class SignUpRequest {
|
|||||||
public int $arrival_eating,
|
public int $arrival_eating,
|
||||||
public int $departure_eating,
|
public int $departure_eating,
|
||||||
public ?string $notes,
|
public ?string $notes,
|
||||||
public Amount $amount
|
public Amount $amount,
|
||||||
|
public ?string $paymentMethod,
|
||||||
|
public array $paymentOptions = [],
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Models\EventParticipant;
|
|||||||
class SignUpResponse {
|
class SignUpResponse {
|
||||||
public bool $success;
|
public bool $success;
|
||||||
public ?EventParticipant $participant;
|
public ?EventParticipant $participant;
|
||||||
|
public ?string $message = null;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->success = false;
|
$this->success = false;
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class UpdateEventRequest {
|
|||||||
public array $contributingLocalGroups;
|
public array $contributingLocalGroups;
|
||||||
public array $eatingHabits;
|
public array $eatingHabits;
|
||||||
|
|
||||||
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits) {
|
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits)
|
||||||
|
{
|
||||||
$this->event = $event;
|
$this->event = $event;
|
||||||
$this->eventName = $eventName;
|
$this->eventName = $eventName;
|
||||||
$this->eventLocation = $eventLocation;
|
$this->eventLocation = $eventLocation;
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ namespace App\Domains\Event\Actions\UpdateEvent;
|
|||||||
|
|
||||||
class UpdateEventResponse {
|
class UpdateEventResponse {
|
||||||
public bool $success;
|
public bool $success;
|
||||||
|
public string $message;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->success = false;
|
$this->success = false;
|
||||||
|
$this->message = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class UpdateParticipantCommand {
|
|||||||
$p->departure_date = DateTime::createFromFormat('Y-m-d', $this->request->departure);
|
$p->departure_date = DateTime::createFromFormat('Y-m-d', $this->request->departure);
|
||||||
$p->participation_type = $this->request->participationType;
|
$p->participation_type = $this->request->participationType;
|
||||||
$p->eating_habit = $this->request->eatingHabit;
|
$p->eating_habit = $this->request->eatingHabit;
|
||||||
|
$p->payment_method = $this->request->paymentMethod;
|
||||||
$p->allergies = $this->request->allergies;
|
$p->allergies = $this->request->allergies;
|
||||||
$p->intolerances = $this->request->intolerances;
|
$p->intolerances = $this->request->intolerances;
|
||||||
$p->medications = $this->request->medications;
|
$p->medications = $this->request->medications;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class UpdateParticipantRequest {
|
|||||||
public string $departure,
|
public string $departure,
|
||||||
public string $participationType,
|
public string $participationType,
|
||||||
public string $eatingHabit,
|
public string $eatingHabit,
|
||||||
|
public ?string $paymentMethod,
|
||||||
public ?string $allergies,
|
public ?string $allergies,
|
||||||
public ?string $intolerances,
|
public ?string $intolerances,
|
||||||
public ?string $medications,
|
public ?string $medications,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class CreateController extends CommonController {
|
|||||||
$registrationFinalEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventRegistrationFinalEnd'));
|
$registrationFinalEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventRegistrationFinalEnd'));
|
||||||
$participationFeeType = ParticipationFeeType::where('slug', $request->input('eventParticipationFeeType'))->first();
|
$participationFeeType = ParticipationFeeType::where('slug', $request->input('eventParticipationFeeType'))->first();
|
||||||
$payPerDay = $request->input('eventPayPerDay');
|
$payPerDay = $request->input('eventPayPerDay');
|
||||||
$payDirect = $request->input('eventPayDirectly');
|
|
||||||
|
|
||||||
$billingDeadline = clone $eventEnd;
|
$billingDeadline = clone $eventEnd;
|
||||||
$billingDeadline->modify('+6 weeks');
|
$billingDeadline->modify('+6 weeks');
|
||||||
@@ -57,8 +56,7 @@ class CreateController extends CommonController {
|
|||||||
$participationFeeType,
|
$participationFeeType,
|
||||||
$request->input('eventAccount'),
|
$request->input('eventAccount'),
|
||||||
$request->input('eventIban'),
|
$request->input('eventIban'),
|
||||||
$payPerDay,
|
$payPerDay
|
||||||
$payDirect
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$wasSuccessful = false;
|
$wasSuccessful = false;
|
||||||
|
|||||||
@@ -4,22 +4,21 @@ namespace App\Domains\Event\Controllers;
|
|||||||
|
|
||||||
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesCommand;
|
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesCommand;
|
||||||
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesRequest;
|
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesRequest;
|
||||||
|
use App\Domains\Event\Actions\SetPaymentMethods\SetPaymentMethodsCommand;
|
||||||
|
use App\Domains\Event\Actions\SetPaymentMethods\SetPaymentMethodsRequest;
|
||||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventCommand;
|
use App\Domains\Event\Actions\UpdateEvent\UpdateEventCommand;
|
||||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventRequest;
|
use App\Domains\Event\Actions\UpdateEvent\UpdateEventRequest;
|
||||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
|
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
|
||||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
|
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
|
||||||
use App\Enumerations\ParticipationFeeType;
|
use App\Enumerations\ParticipationFeeType;
|
||||||
use App\Enumerations\ParticipationType;
|
use App\Enumerations\ParticipationType;
|
||||||
use App\Models\EventParticipant;
|
|
||||||
use App\Providers\InertiaProvider;
|
use App\Providers\InertiaProvider;
|
||||||
use App\Providers\PdfGenerateAndDownloadProvider;
|
use App\Providers\PdfGenerateAndDownloadProvider;
|
||||||
use App\Resources\EventResource;
|
|
||||||
use App\Scopes\CommonController;
|
use App\Scopes\CommonController;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use function Symfony\Component\String\b;
|
|
||||||
|
|
||||||
class DetailsController extends CommonController {
|
class DetailsController extends CommonController {
|
||||||
public function __invoke(string $eventId) {
|
public function __invoke(string $eventId) {
|
||||||
@@ -63,7 +62,21 @@ class DetailsController extends CommonController {
|
|||||||
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
||||||
$response = $eventUpdateCommand->execute();
|
$response = $eventUpdateCommand->execute();
|
||||||
|
|
||||||
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
return response()->json(['status' => $response->success ? 'success' : 'error', 'message' => $response->message]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePaymentMethods(int $eventId, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
|
||||||
|
$paymentMethods = $request->input('paymentMethods', []);
|
||||||
|
$paymentMethodConfigurations = (array)$request->input('paymentMethodConfigurations', []);
|
||||||
|
|
||||||
|
$setPaymentMethodsRequest = new SetPaymentMethodsRequest($event, $paymentMethods, $paymentMethodConfigurations);
|
||||||
|
$setPaymentMethodsCommand = new SetPaymentMethodsCommand($setPaymentMethodsRequest);
|
||||||
|
$response = $setPaymentMethodsCommand->execute();
|
||||||
|
|
||||||
|
return response()->json(['status' => $response->success ? 'success' : 'error', 'message' => $response->message]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateEventManagers(int $eventId, Request $request) : JsonResponse {
|
public function updateEventManagers(int $eventId, Request $request) : JsonResponse {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class ParticipantUpdateController extends CommonController {
|
|||||||
departure: $request->input('departure'),
|
departure: $request->input('departure'),
|
||||||
participationType: $request->input('participationType'),
|
participationType: $request->input('participationType'),
|
||||||
eatingHabit: $request->input('eatingHabit'),
|
eatingHabit: $request->input('eatingHabit'),
|
||||||
|
paymentMethod: $request->input('paymentMethod'),
|
||||||
allergies: $request->input('allergies'),
|
allergies: $request->input('allergies'),
|
||||||
intolerances: $request->input('intolerances'),
|
intolerances: $request->input('intolerances'),
|
||||||
medications: $request->input('medications'),
|
medications: $request->input('medications'),
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ class SignupController extends CommonController {
|
|||||||
$availableEvents[] = $event->toResource()->toArray($request);
|
$availableEvents[] = $event->toResource()->toArray($request);
|
||||||
};
|
};
|
||||||
|
|
||||||
$event = $this->events->getByIdentifier($eventId, false)?->toResource()->toArray($request);
|
$eventModel = $this->events->getByIdentifier($eventId, false);
|
||||||
|
$event = $eventModel?->toResource()->toArray($request);
|
||||||
|
// Die (aktiven) Zahlungsmethoden stehen dem Frontend über $event['paymentMethods'] zur Verfügung;
|
||||||
|
// die Auswahl trifft der/die Teilnehmer\*in im Anmeldeschritt "Zahlungsart".
|
||||||
|
|
||||||
$participantData = [
|
$participantData = [
|
||||||
'firstname' => '',
|
'firstname' => '',
|
||||||
@@ -68,6 +71,11 @@ class SignupController extends CommonController {
|
|||||||
|
|
||||||
public function signUp(int $eventId, Request $request) {
|
public function signUp(int $eventId, Request $request) {
|
||||||
$event = $this->events->getById($eventId, false);
|
$event = $this->events->getById($eventId, false);
|
||||||
|
|
||||||
|
if (!$event->registration_allowed || new \DateTime() > $event->start_date) {
|
||||||
|
return response()->json(['status' => 'closed'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
$eventResource = $event->toResource();
|
$eventResource = $event->toResource();
|
||||||
$registrationData = $request->input('registration_data');
|
$registrationData = $request->input('registration_data');
|
||||||
$siblingReduction = $registrationData['sibling'] === 'true';
|
$siblingReduction = $registrationData['sibling'] === 'true';
|
||||||
@@ -131,12 +139,18 @@ class SignupController extends CommonController {
|
|||||||
$registrationData['anreise_essen'],
|
$registrationData['anreise_essen'],
|
||||||
$registrationData['abreise_essen'],
|
$registrationData['abreise_essen'],
|
||||||
$registrationData['anmerkungen'],
|
$registrationData['anmerkungen'],
|
||||||
$amount
|
$amount,
|
||||||
|
$registrationData['paymentMethod'] ?? null,
|
||||||
|
(array)($registrationData['paymentOptions'] ?? []),
|
||||||
);
|
);
|
||||||
|
|
||||||
$signupCommand = new SignUpCommand($signupRequest);
|
$signupCommand = new SignUpCommand($signupRequest);
|
||||||
$signupResponse = $signupCommand->execute();
|
$signupResponse = $signupCommand->execute();
|
||||||
|
|
||||||
|
if (!$signupResponse->success) {
|
||||||
|
return response()->json(['status' => 'error', 'message' => $signupResponse->message], 422);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Addons registrieren
|
// 4. Addons registrieren
|
||||||
|
|
||||||
|
|
||||||
@@ -170,14 +184,18 @@ class SignupController extends CommonController {
|
|||||||
|
|
||||||
$siblingReduction = $request->input('sibling') === 'true';
|
$siblingReduction = $request->input('sibling') === 'true';
|
||||||
|
|
||||||
return response()->json(['amount' =>
|
$amount = $event->calculateAmount(
|
||||||
$event->calculateAmount(
|
|
||||||
$request->input('participationType'),
|
$request->input('participationType'),
|
||||||
$request->input('beitrag'),
|
$request->input('beitrag'),
|
||||||
\DateTime::createFromFormat('Y-m-d', $request->input('arrival')),
|
\DateTime::createFromFormat('Y-m-d', $request->input('arrival')),
|
||||||
\DateTime::createFromFormat('Y-m-d', $request->input('departure')),
|
\DateTime::createFromFormat('Y-m-d', $request->input('departure')),
|
||||||
$siblingReduction
|
$siblingReduction
|
||||||
)->toString()
|
);
|
||||||
|
|
||||||
|
// amountValue (numerisch) steuert im Frontend das Überspringen des Zahlungsart-Schritts bei 0 €.
|
||||||
|
return response()->json([
|
||||||
|
'amount' => $amount->toString(),
|
||||||
|
'amountValue' => $amount->getAmount(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ Route::prefix('api/v1')
|
|||||||
Route::post('/event-managers', [DetailsController::class, 'updateEventManagers']);
|
Route::post('/event-managers', [DetailsController::class, 'updateEventManagers']);
|
||||||
Route::post('/participation-fees', [DetailsController::class, 'updateParticipationFees']);
|
Route::post('/participation-fees', [DetailsController::class, 'updateParticipationFees']);
|
||||||
Route::post('/common-settings', [DetailsController::class, 'updateCommonSettings']);
|
Route::post('/common-settings', [DetailsController::class, 'updateCommonSettings']);
|
||||||
|
Route::post('/payment-methods', [DetailsController::class, 'updatePaymentMethods']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
eventRegistrationFinalEnd: '',
|
eventRegistrationFinalEnd: '',
|
||||||
eventAccount: props.eventAccount ? props.eventAccount : '',
|
eventAccount: props.eventAccount ? props.eventAccount : '',
|
||||||
eventIban: props.eventIban ? props.eventIban : '',
|
eventIban: props.eventIban ? props.eventIban : '',
|
||||||
eventPayDirectly: true,
|
|
||||||
eventPayPerDay: props.eventPayPerDay ? props.eventPayPerDay : false,
|
eventPayPerDay: props.eventPayPerDay ? props.eventPayPerDay : false,
|
||||||
eventParticipationFeeType: props.participationFeeType ? props.participationFeeType : 'fixed',
|
eventParticipationFeeType: props.participationFeeType ? props.participationFeeType : 'fixed',
|
||||||
});
|
});
|
||||||
@@ -154,7 +153,6 @@
|
|||||||
eventRegistrationFinalEnd: formData.eventRegistrationFinalEnd,
|
eventRegistrationFinalEnd: formData.eventRegistrationFinalEnd,
|
||||||
eventAccount: formData.eventAccount,
|
eventAccount: formData.eventAccount,
|
||||||
eventIban: formData.eventIban,
|
eventIban: formData.eventIban,
|
||||||
eventPayDirectly: formData.eventPayDirectly,
|
|
||||||
eventPayPerDay: formData.eventPayPerDay,
|
eventPayPerDay: formData.eventPayPerDay,
|
||||||
eventParticipationFeeType: formData.eventParticipationFeeType,
|
eventParticipationFeeType: formData.eventParticipationFeeType,
|
||||||
}
|
}
|
||||||
@@ -262,13 +260,6 @@
|
|||||||
<ErrorText :message="errors.eventIban" /></td>
|
<ErrorText :message="errors.eventIban" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr style="vertical-align: top;">
|
|
||||||
<td colspan="2" style="font-weight: bold;">
|
|
||||||
<input type="checkbox" v-model="formData.eventPayDirectly">
|
|
||||||
Teilnehmende zahlen direkt aufs Veranstaltungskonto
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr style="vertical-align: top;">
|
<tr style="vertical-align: top;">
|
||||||
<td colspan="2" style="font-weight: bold;">
|
<td colspan="2" style="font-weight: bold;">
|
||||||
<input type="checkbox" v-model="formData.eventPayPerDay">
|
<input type="checkbox" v-model="formData.eventPayPerDay">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user