Support oft parent groups
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateParentGroup;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Models\ParentGroup;
|
||||
|
||||
class CreateParentGroupAction
|
||||
{
|
||||
public function __construct(private CreateParentGroupRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): CreateParentGroupResponse
|
||||
{
|
||||
$response = new CreateParentGroupResponse();
|
||||
|
||||
$name = trim($this->request->name);
|
||||
|
||||
if ($name === '') {
|
||||
$response->message = 'Bitte einen Namen angeben.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->parentGroup = ParentGroup::create([
|
||||
'name' => $name,
|
||||
'is_fallback' => false,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = sprintf('%s „%s" wurde angelegt.', ParticipantGroupType::ParentGroup->label(), $name);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateParentGroup;
|
||||
|
||||
class CreateParentGroupRequest
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateParentGroup;
|
||||
|
||||
use App\Models\ParentGroup;
|
||||
|
||||
class CreateParentGroupResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
public ?ParentGroup $parentGroup = null;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Models\AvailablePaymentMethod;
|
||||
use App\Models\PaymentMethod;
|
||||
use App\Models\Tenant;
|
||||
@@ -16,10 +17,17 @@ class CreateTenantAction
|
||||
{
|
||||
$response = new CreateTenantResponse();
|
||||
|
||||
if ($this->request->parentGroup === null) {
|
||||
$response->message = sprintf('Bitte bei „%s" eine Auswahl treffen.', ParticipantGroupType::ParentGroup->label());
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'name' => $this->request->name,
|
||||
'slug' => $this->request->slug,
|
||||
'url' => $this->request->url,
|
||||
'parent_group_id' => $this->request->parentGroup->id,
|
||||
'email' => '',
|
||||
'email_finance' => '',
|
||||
'account_name' => '',
|
||||
@@ -46,7 +54,7 @@ class CreateTenantAction
|
||||
}
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Stamm wurde angelegt.';
|
||||
$response->message = sprintf('%s wurde angelegt.', ParticipantGroupType::LocalGroup->label());
|
||||
$response->slug = $tenant->slug;
|
||||
|
||||
return $response;
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||
|
||||
use App\Models\ParentGroup;
|
||||
|
||||
class CreateTenantRequest
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $slug,
|
||||
public string $url,
|
||||
/** Pflicht; null heißt, die übermittelte ID war unbekannt. */
|
||||
public ?ParentGroup $parentGroup,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\DeleteParentGroup;
|
||||
|
||||
class DeleteParentGroupAction
|
||||
{
|
||||
public function __construct(private DeleteParentGroupRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): DeleteParentGroupResponse
|
||||
{
|
||||
$response = new DeleteParentGroupResponse();
|
||||
|
||||
// Die Fallback-Gruppe nimmt Mandanten ohne Zuordnung auf und muss deshalb bestehen bleiben.
|
||||
if ($this->request->parentGroup->is_fallback) {
|
||||
$response->message = sprintf('„%s" kann nicht gelöscht werden.', $this->request->parentGroup->name);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($this->request->inUse) {
|
||||
$response->message = sprintf(
|
||||
'„%s" ist noch zugeordnet und kann deshalb nicht gelöscht werden.',
|
||||
$this->request->parentGroup->name,
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->request->parentGroup->delete();
|
||||
|
||||
$response->success = true;
|
||||
$response->message = sprintf('„%s" wurde gelöscht.', $this->request->parentGroup->name);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\DeleteParentGroup;
|
||||
|
||||
use App\Models\ParentGroup;
|
||||
|
||||
class DeleteParentGroupRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ParentGroup $parentGroup,
|
||||
/** Ob die Gruppe noch von einem Mandanten oder einer Anmeldung verwendet wird. */
|
||||
public bool $inUse,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\DeleteParentGroup;
|
||||
|
||||
class DeleteParentGroupResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\RenameParentGroup;
|
||||
|
||||
class RenameParentGroupAction
|
||||
{
|
||||
public function __construct(private RenameParentGroupRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): RenameParentGroupResponse
|
||||
{
|
||||
$response = new RenameParentGroupResponse();
|
||||
|
||||
$name = trim($this->request->name);
|
||||
|
||||
if ($name === '') {
|
||||
$response->message = 'Bitte einen Namen angeben.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Umbenennen ist jederzeit möglich: Mandanten und Anmeldungen verweisen über die ID.
|
||||
$this->request->parentGroup->update(['name' => $name]);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Der Name wurde gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\RenameParentGroup;
|
||||
|
||||
use App\Models\ParentGroup;
|
||||
|
||||
class RenameParentGroupRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ParentGroup $parentGroup,
|
||||
public string $name,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\RenameParentGroup;
|
||||
|
||||
class RenameParentGroupResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateGlobalSettings;
|
||||
|
||||
use App\Models\GlobalSetting;
|
||||
|
||||
class UpdateGlobalSettingsAction
|
||||
{
|
||||
public function __construct(private UpdateGlobalSettingsRequest $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function execute(): UpdateGlobalSettingsResponse
|
||||
{
|
||||
$response = new UpdateGlobalSettingsResponse();
|
||||
|
||||
$values = [
|
||||
GlobalSetting::LOCAL_GROUP_LABEL => trim($this->request->localGroupLabel),
|
||||
GlobalSetting::LOCAL_GROUP_LABEL_PLURAL => trim($this->request->localGroupLabelPlural),
|
||||
GlobalSetting::PARENT_GROUP_LABEL => trim($this->request->parentGroupLabel),
|
||||
GlobalSetting::PARENT_GROUP_LABEL_PLURAL => trim($this->request->parentGroupLabelPlural),
|
||||
];
|
||||
|
||||
// Eine leere Bezeichnung ließe Überschriften und Auswahlfelder ohne Beschriftung zurück.
|
||||
if (in_array('', $values, true)) {
|
||||
$response->message = 'Bitte alle Bezeichnungen ausfüllen.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->request->globalSettings->setMany($values);
|
||||
|
||||
$response->success = true;
|
||||
$response->message = 'Einstellungen wurden gespeichert.';
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateGlobalSettings;
|
||||
|
||||
use App\Repositories\GlobalSettingRepository;
|
||||
|
||||
class UpdateGlobalSettingsRequest
|
||||
{
|
||||
public function __construct(
|
||||
public GlobalSettingRepository $globalSettings,
|
||||
public string $localGroupLabel,
|
||||
public string $localGroupLabelPlural,
|
||||
public string $parentGroupLabel,
|
||||
public string $parentGroupLabelPlural,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateGlobalSettings;
|
||||
|
||||
class UpdateGlobalSettingsResponse
|
||||
{
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
|
||||
class UpdateTenantGeneralAction
|
||||
{
|
||||
public function __construct(private UpdateTenantGeneralRequest $request)
|
||||
@@ -12,10 +14,17 @@ class UpdateTenantGeneralAction
|
||||
{
|
||||
$response = new UpdateTenantGeneralResponse();
|
||||
|
||||
if ($this->request->parentGroup === null) {
|
||||
$response->message = sprintf('Bitte bei „%s" eine Auswahl treffen.', ParticipantGroupType::ParentGroup->label());
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->request->tenant->update([
|
||||
'name' => $this->request->name,
|
||||
'slug' => $this->request->slug,
|
||||
'url' => $this->request->url,
|
||||
'parent_group_id' => $this->request->parentGroup->id,
|
||||
]);
|
||||
|
||||
$response->success = true;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||
|
||||
use App\Models\ParentGroup;
|
||||
use App\Models\Tenant;
|
||||
|
||||
class UpdateTenantGeneralRequest
|
||||
@@ -11,6 +12,8 @@ class UpdateTenantGeneralRequest
|
||||
public string $name,
|
||||
public string $slug,
|
||||
public string $url,
|
||||
/** Pflicht; null heißt, die übermittelte ID war unbekannt. */
|
||||
public ?ParentGroup $parentGroup,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GlobalSettingsGetController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'groupLabels' => ParticipantGroupType::labels(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class GlobalSettingsPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
return new InertiaProvider('Admin/GlobalSettings', [])->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateGlobalSettings\UpdateGlobalSettingsAction;
|
||||
use App\Domains\Admin\Actions\UpdateGlobalSettings\UpdateGlobalSettingsRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GlobalSettingsUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$response = new UpdateGlobalSettingsAction(new UpdateGlobalSettingsRequest(
|
||||
globalSettings: $this->globalSettings,
|
||||
localGroupLabel: (string) $request->input('localGroup'),
|
||||
localGroupLabelPlural: (string) $request->input('localGroupPlural'),
|
||||
parentGroupLabel: (string) $request->input('parentGroup'),
|
||||
parentGroupLabelPlural: (string) $request->input('parentGroupPlural'),
|
||||
))->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\CreateParentGroup\CreateParentGroupAction;
|
||||
use App\Domains\Admin\Actions\CreateParentGroup\CreateParentGroupRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParentGroupCreateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$response = new CreateParentGroupAction(new CreateParentGroupRequest(
|
||||
name: (string) $request->input('name'),
|
||||
))->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\DeleteParentGroup\DeleteParentGroupAction;
|
||||
use App\Domains\Admin\Actions\DeleteParentGroup\DeleteParentGroupRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParentGroupDeleteController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$parentGroup = $this->adminParentGroups->findById($id);
|
||||
|
||||
if ($parentGroup === null) {
|
||||
return response()->json(['status' => 'error', 'message' => 'Eintrag nicht gefunden.'], 404);
|
||||
}
|
||||
|
||||
$response = new DeleteParentGroupAction(new DeleteParentGroupRequest(
|
||||
parentGroup: $parentGroup,
|
||||
inUse: $this->adminParentGroups->isInUse($parentGroup),
|
||||
))->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 ParentGroupListApiController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$parentGroups = $this->adminParentGroups->all()->map(fn ($parentGroup) => [
|
||||
'id' => $parentGroup->id,
|
||||
'name' => $parentGroup->name,
|
||||
'isFallback' => $parentGroup->is_fallback,
|
||||
'tenantCount' => $parentGroup->tenants_count,
|
||||
'deletable' => !$parentGroup->is_fallback && !$this->adminParentGroups->isInUse($parentGroup),
|
||||
]);
|
||||
|
||||
return response()->json(['parentGroups' => $parentGroups]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class ParentGroupListPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
return new InertiaProvider('Admin/ParentGroupList', [
|
||||
'groupLabels' => ParticipantGroupType::labels(),
|
||||
])->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\RenameParentGroup\RenameParentGroupAction;
|
||||
use App\Domains\Admin\Actions\RenameParentGroup\RenameParentGroupRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ParentGroupUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$parentGroup = $this->adminParentGroups->findById($id);
|
||||
|
||||
if ($parentGroup === null) {
|
||||
return response()->json(['status' => 'error', 'message' => 'Eintrag nicht gefunden.'], 404);
|
||||
}
|
||||
|
||||
$response = new RenameParentGroupAction(new RenameParentGroupRequest(
|
||||
parentGroup: $parentGroup,
|
||||
name: (string) $request->input('name'),
|
||||
))->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => $response->success ? 'success' : 'error',
|
||||
'message' => $response->message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class TenantCreateController extends CommonController
|
||||
name: $request->input('name'),
|
||||
slug: $request->input('slug'),
|
||||
url: $request->input('url'),
|
||||
parentGroup: $this->adminParentGroups->findById((int) $request->input('parent_group_id')),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -17,6 +18,12 @@ class TenantGeneralGetController extends CommonController
|
||||
'slug' => $tenant->slug,
|
||||
'url' => $tenant->url,
|
||||
'is_active_local_group' => $tenant->is_active_local_group,
|
||||
'parent_group_id' => $tenant->parent_group_id,
|
||||
'parentGroups' => $this->adminParentGroups->all()->map(fn ($parentGroup) => [
|
||||
'id' => $parentGroup->id,
|
||||
'name' => $parentGroup->name,
|
||||
]),
|
||||
'parentGroupLabel' => ParticipantGroupType::ParentGroup->label(),
|
||||
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/general',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class TenantGeneralUpdateController extends CommonController
|
||||
name: $request->input('name'),
|
||||
slug: $request->input('slug'),
|
||||
url: $request->input('url'),
|
||||
parentGroup: $this->adminParentGroups->findById((int) $request->input('parent_group_id')),
|
||||
));
|
||||
|
||||
$response = $action->execute();
|
||||
|
||||
@@ -18,9 +18,17 @@ class TenantListApiController extends CommonController
|
||||
'slug' => $tenant->slug,
|
||||
'url' => $tenant->url,
|
||||
'is_active_local_group' => $tenant->is_active_local_group,
|
||||
'parentGroup' => $tenant->parentGroup?->name,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json(['tenants' => $mapped]);
|
||||
// Auswahl für das Anlage-Formular (Pflichtangabe).
|
||||
$parentGroups = $this->adminParentGroups->all()->map(fn ($parentGroup) => [
|
||||
'id' => $parentGroup->id,
|
||||
'name' => $parentGroup->name,
|
||||
'isFallback' => $parentGroup->is_fallback,
|
||||
]);
|
||||
|
||||
return response()->json(['tenants' => $mapped, 'parentGroups' => $parentGroups]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -11,7 +12,9 @@ class TenantListPageController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Admin/TenantList', []);
|
||||
$inertiaProvider = new InertiaProvider('Admin/TenantList', [
|
||||
'groupLabels' => ParticipantGroupType::labels(),
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Admin\Repositories;
|
||||
|
||||
use App\Models\ParentGroup;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class AdminParentGroupRepository
|
||||
{
|
||||
/** Alle Parent Groups, alphabetisch mit „Ohne Zuordnung" am Ende, mit der Zahl zugeordneter Mandanten. */
|
||||
public function all(): Collection
|
||||
{
|
||||
return ParentGroup::withCount('tenants')
|
||||
->ordered()
|
||||
->get();
|
||||
}
|
||||
|
||||
public function findById(int $id): ?ParentGroup
|
||||
{
|
||||
return ParentGroup::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ob die Parent Group noch verwendet wird -- von einem Mandanten oder von einer Anmeldung. Anmeldungen sind
|
||||
* mandantengebunden; gezählt wird hier bewusst über alle Mandanten hinweg.
|
||||
*/
|
||||
public function isInUse(ParentGroup $parentGroup): bool
|
||||
{
|
||||
return $parentGroup->tenants()->exists()
|
||||
|| $parentGroup->eventParticipants()->withoutGlobalScopes()->exists();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ use App\Domains\Admin\Controllers\DocumentAssetUpdateController;
|
||||
use App\Domains\Admin\Controllers\DocumentTemplatesGetController;
|
||||
use App\Domains\Admin\Controllers\DocumentTemplatesPreviewController;
|
||||
use App\Domains\Admin\Controllers\DocumentTemplatesUpdateController;
|
||||
use App\Domains\Admin\Controllers\GlobalSettingsGetController;
|
||||
use App\Domains\Admin\Controllers\GlobalSettingsUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantContactGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantContactUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantGdprGetController;
|
||||
@@ -15,6 +17,10 @@ use App\Domains\Admin\Controllers\ManagedTenantPaymentGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantPaymentUpdateController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantTaxGetController;
|
||||
use App\Domains\Admin\Controllers\ManagedTenantTaxUpdateController;
|
||||
use App\Domains\Admin\Controllers\ParentGroupCreateController;
|
||||
use App\Domains\Admin\Controllers\ParentGroupDeleteController;
|
||||
use App\Domains\Admin\Controllers\ParentGroupListApiController;
|
||||
use App\Domains\Admin\Controllers\ParentGroupUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantContactGetController;
|
||||
use App\Domains\Admin\Controllers\TenantContactUpdateController;
|
||||
use App\Domains\Admin\Controllers\TenantCreateController;
|
||||
@@ -76,6 +82,12 @@ Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->
|
||||
Route::post('/assets', DocumentAssetUpdateController::class);
|
||||
Route::delete('/assets/{name}', DocumentAssetDeleteController::class);
|
||||
});
|
||||
|
||||
// Bezeichnungen der Gruppen-Ebenen gelten ebenfalls app-weit.
|
||||
Route::prefix('api/v1/admin/settings')->group(function () {
|
||||
Route::get('/', GlobalSettingsGetController::class);
|
||||
Route::post('/', GlobalSettingsUpdateController::class);
|
||||
});
|
||||
});
|
||||
|
||||
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||
@@ -97,5 +109,12 @@ Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->
|
||||
Route::post('/tax', ManagedTenantTaxUpdateController::class);
|
||||
});
|
||||
});
|
||||
|
||||
Route::prefix('api/v1/admin/parent-groups')->group(function () {
|
||||
Route::get('/list', ParentGroupListApiController::class);
|
||||
Route::post('/create', ParentGroupCreateController::class);
|
||||
Route::post('/{id}', ParentGroupUpdateController::class)->whereNumber('id');
|
||||
Route::delete('/{id}', ParentGroupDeleteController::class)->whereNumber('id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use App\Domains\Admin\Controllers\AdminDashboardController;
|
||||
use App\Domains\Admin\Controllers\DocumentTemplatesPageController;
|
||||
use App\Domains\Admin\Controllers\GlobalSettingsPageController;
|
||||
use App\Domains\Admin\Controllers\ParentGroupListPageController;
|
||||
use App\Domains\Admin\Controllers\TenantEditPageController;
|
||||
use App\Domains\Admin\Controllers\TenantListPageController;
|
||||
use App\Domains\Admin\Controllers\TenantPageController;
|
||||
@@ -21,11 +23,13 @@ Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->
|
||||
// App-weit gültige Dokumentvorlagen -- nur für Hauptadministrator*innen.
|
||||
Route::middleware(MainAdminRoleMiddleware::class)->group(function () {
|
||||
Route::get('/document-templates', DocumentTemplatesPageController::class);
|
||||
Route::get('/settings', GlobalSettingsPageController::class);
|
||||
});
|
||||
|
||||
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||
Route::get('/tenants', TenantListPageController::class);
|
||||
Route::get('/tenants/{slug}', TenantEditPageController::class);
|
||||
Route::get('/parent-groups', ParentGroupListPageController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<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()
|
||||
|
||||
const form = ref({
|
||||
localGroup: '',
|
||||
localGroupPlural: '',
|
||||
parentGroup: '',
|
||||
parentGroupPlural: '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await request('/api/v1/admin/settings')
|
||||
Object.assign(form.value, response?.groupLabels ?? {})
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const response = await request('/api/v1/admin/settings', {
|
||||
method: 'POST',
|
||||
body: form.value,
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Einstellungen">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<h3>Bezeichnungen der Gruppen</h3>
|
||||
<p class="hint">
|
||||
Gelten für alle Mandanten: in Anmeldungen, Teilnehmendenlisten, Exporten und in der Verwaltung.
|
||||
</p>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Einzahl</th>
|
||||
<th>Mehrzahl</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Gruppe (Mandant)</th>
|
||||
<td><input type="text" v-model="form.localGroup" class="form-input" placeholder="z. B. Stamm" /></td>
|
||||
<td><input type="text" v-model="form.localGroupPlural" class="form-input" placeholder="z. B. Stämme" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Übergeordnete Gruppe</th>
|
||||
<td><input type="text" v-model="form.parentGroup" class="form-input" placeholder="z. B. Bund" /></td>
|
||||
<td><input type="text" v-model="form.parentGroupPlural" class="form-input" placeholder="z. B. Bünde" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button class="btn-save" @click="save">Speichern</button>
|
||||
</shadowed-box>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hint {
|
||||
color: #6b7280;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
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-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,249 @@
|
||||
<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 props = defineProps({
|
||||
groupLabels: {
|
||||
type: Object,
|
||||
default: () => ({ localGroupPlural: 'Stämme', parentGroup: 'Bund', parentGroupPlural: 'Bünde' }),
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
const parentGroups = ref([])
|
||||
const newName = ref('')
|
||||
const showCreate = ref(false)
|
||||
// ID des Eintrags, der gerade umbenannt wird, samt Entwurf des neuen Namens.
|
||||
const editingId = ref(null)
|
||||
const editName = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
await loadParentGroups()
|
||||
})
|
||||
|
||||
async function loadParentGroups() {
|
||||
const response = await request('/api/v1/admin/parent-groups/list')
|
||||
parentGroups.value = response?.parentGroups ?? []
|
||||
}
|
||||
|
||||
async function createParentGroup() {
|
||||
const response = await request('/api/v1/admin/parent-groups/create', {
|
||||
method: 'POST',
|
||||
body: { name: newName.value },
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
newName.value = ''
|
||||
showCreate.value = false
|
||||
await loadParentGroups()
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Anlegen')
|
||||
}
|
||||
}
|
||||
|
||||
function startEditing(parentGroup) {
|
||||
editingId.value = parentGroup.id
|
||||
editName.value = parentGroup.name
|
||||
}
|
||||
|
||||
async function saveName(parentGroup) {
|
||||
const response = await request('/api/v1/admin/parent-groups/' + parentGroup.id, {
|
||||
method: 'POST',
|
||||
body: { name: editName.value },
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
editingId.value = null
|
||||
await loadParentGroups()
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteParentGroup(parentGroup) {
|
||||
if (!confirm('„' + parentGroup.name + '" wirklich löschen?')) {
|
||||
return
|
||||
}
|
||||
|
||||
const response = await request('/api/v1/admin/parent-groups/' + parentGroup.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
await loadParentGroups()
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Löschen')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout :title="props.groupLabels.parentGroupPlural">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<table class="parent-group-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Zugeordnete {{ props.groupLabels.localGroupPlural }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="parentGroup in parentGroups" :key="parentGroup.id">
|
||||
<td>
|
||||
<input v-if="editingId === parentGroup.id" type="text" v-model="editName" class="form-input"
|
||||
@keyup.enter="saveName(parentGroup)" />
|
||||
<span v-else>{{ parentGroup.name }}</span>
|
||||
</td>
|
||||
<td>{{ parentGroup.tenantCount }}</td>
|
||||
<td class="actions">
|
||||
<template v-if="editingId === parentGroup.id">
|
||||
<button class="btn-save" @click="saveName(parentGroup)">Speichern</button>
|
||||
<button class="btn-cancel" @click="editingId = null">Abbrechen</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="btn-edit" @click="startEditing(parentGroup)">Umbenennen</button>
|
||||
<button v-if="parentGroup.deletable" class="btn-delete" @click="deleteParentGroup(parentGroup)">Löschen</button>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="hint">
|
||||
Löschen ist nur möglich, solange keine {{ props.groupLabels.localGroupPlural }} und keine Anmeldungen
|
||||
zugeordnet sind. „Ohne Zuordnung" bleibt immer bestehen.
|
||||
</p>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button v-if="!showCreate" class="btn-create" @click="showCreate = true">Neuen Eintrag anlegen</button>
|
||||
|
||||
<div v-if="showCreate" class="create-form">
|
||||
<h3>Neuen Eintrag anlegen ({{ props.groupLabels.parentGroup }})</h3>
|
||||
<input type="text" v-model="newName" class="form-input" placeholder="Name"
|
||||
@keyup.enter="createParentGroup" />
|
||||
<div class="btn-group">
|
||||
<button class="btn-save" @click="createParentGroup">Anlegen</button>
|
||||
<button class="btn-cancel" @click="showCreate = false">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</shadowed-box>
|
||||
</AdminAppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.parent-group-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.parent-group-table thead th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
background-color: #f9fafb;
|
||||
color: #374151;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #d1d5db;
|
||||
}
|
||||
|
||||
.parent-group-table tbody td {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 12px;
|
||||
color: #6b7280;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.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-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.btn-create, .btn-save, .btn-cancel, .btn-edit, .btn-delete {
|
||||
padding: 6px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-create, .btn-edit {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-create:hover, .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-delete {
|
||||
background-color: #dc2626;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background-color: #b91c1c;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
@@ -17,8 +17,16 @@ const form = ref({
|
||||
name: props.data.name ?? '',
|
||||
slug: props.data.slug ?? '',
|
||||
url: props.data.url ?? '',
|
||||
parent_group_id: props.data.parent_group_id ?? null,
|
||||
})
|
||||
|
||||
// Übergeordnete Gruppe (z. B. „Bund") -- Pflichtangabe, Auswahl aus der LV-seitig gepflegten Liste.
|
||||
const parentGroups = props.data.parentGroups ?? []
|
||||
const parentGroupLabel = props.data.parentGroupLabel ?? 'Bund'
|
||||
const parentGroupName = computed(() =>
|
||||
parentGroups.find(g => g.id === form.value.parent_group_id)?.name ?? '—'
|
||||
)
|
||||
|
||||
async function save() {
|
||||
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenants/' + props.data.slug + '/general'
|
||||
const response = await request(saveUrl, {
|
||||
@@ -39,6 +47,7 @@ async function save() {
|
||||
<div v-if="!editing">
|
||||
<table class="data-table">
|
||||
<tr><th>Name:</th><td>{{ form.name }}</td></tr>
|
||||
<tr><th>{{ parentGroupLabel }}:</th><td>{{ parentGroupName }}</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>
|
||||
@@ -54,6 +63,14 @@ async function save() {
|
||||
<th>Name:</th>
|
||||
<td><input type="text" v-model="form.name" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ parentGroupLabel }}:</th>
|
||||
<td>
|
||||
<select v-model="form.parent_group_id" class="form-input">
|
||||
<option v-for="group in parentGroups" :key="group.id" :value="group.id">{{ group.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Slug:</th>
|
||||
<td><input type="text" v-model="form.slug" class="form-input" /></td>
|
||||
|
||||
@@ -5,6 +5,13 @@ import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import { toast } from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
groupLabels: {
|
||||
type: Object,
|
||||
default: () => ({ localGroup: 'Stamm', localGroupPlural: 'Stämme', parentGroup: 'Bund' }),
|
||||
},
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
|
||||
function openTenant(slug) {
|
||||
@@ -12,12 +19,16 @@ function openTenant(slug) {
|
||||
}
|
||||
|
||||
const tenants = ref([])
|
||||
const parentGroups = ref([])
|
||||
const showCreate = ref(false)
|
||||
const createForm = ref({
|
||||
const emptyForm = () => ({
|
||||
name: '',
|
||||
slug: '',
|
||||
url: '',
|
||||
// Pflichtangabe; vorbelegt mit „Ohne Zuordnung", sobald die Liste geladen ist.
|
||||
parent_group_id: parentGroups.value.find(g => g.isFallback)?.id ?? null,
|
||||
})
|
||||
const createForm = ref(emptyForm())
|
||||
|
||||
onMounted(async () => {
|
||||
await loadTenants()
|
||||
@@ -33,6 +44,11 @@ async function loadTenants() {
|
||||
})
|
||||
const data = await response.json()
|
||||
tenants.value = data.tenants
|
||||
parentGroups.value = data.parentGroups ?? []
|
||||
|
||||
if (createForm.value.parent_group_id === null) {
|
||||
createForm.value.parent_group_id = parentGroups.value.find(g => g.isFallback)?.id ?? null
|
||||
}
|
||||
}
|
||||
|
||||
async function createTenant() {
|
||||
@@ -44,7 +60,7 @@ async function createTenant() {
|
||||
if (response && response.status === 'success') {
|
||||
toast.success(response.message)
|
||||
showCreate.value = false
|
||||
createForm.value = { name: '', slug: '', url: '' }
|
||||
createForm.value = emptyForm()
|
||||
await loadTenants()
|
||||
} else {
|
||||
toast.error(response?.message ?? 'Fehler beim Anlegen')
|
||||
@@ -53,12 +69,13 @@ async function createTenant() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Stämme">
|
||||
<AdminAppLayout :title="props.groupLabels.localGroupPlural">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<table class="tenant-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>{{ props.groupLabels.parentGroup }}</th>
|
||||
<th>Slug</th>
|
||||
<th>URL</th>
|
||||
<th>Status</th>
|
||||
@@ -67,6 +84,7 @@ async function createTenant() {
|
||||
<tbody>
|
||||
<tr v-for="tenant in tenants" :key="tenant.slug" class="tenant-row" @click="openTenant(tenant.slug)">
|
||||
<td>{{ tenant.name }}</td>
|
||||
<td>{{ tenant.parentGroup ?? '—' }}</td>
|
||||
<td>{{ tenant.slug }}</td>
|
||||
<td>{{ tenant.url }}</td>
|
||||
<td>
|
||||
@@ -79,15 +97,23 @@ async function createTenant() {
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button v-if="!showCreate" class="btn-create" @click="showCreate = true">Neuen Stamm anlegen</button>
|
||||
<button v-if="!showCreate" class="btn-create" @click="showCreate = true">Neuen Eintrag anlegen ({{ props.groupLabels.localGroup }})</button>
|
||||
|
||||
<div v-if="showCreate" class="create-form">
|
||||
<h3>Neuen Stamm anlegen</h3>
|
||||
<h3>Neuen Eintrag anlegen ({{ props.groupLabels.localGroup }})</h3>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td><input type="text" v-model="createForm.name" class="form-input" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ props.groupLabels.parentGroup }}</th>
|
||||
<td>
|
||||
<select v-model="createForm.parent_group_id" class="form-input">
|
||||
<option v-for="group in parentGroups" :key="group.id" :value="group.id">{{ group.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Slug</th>
|
||||
<td><input type="text" v-model="createForm.slug" class="form-input" /></td>
|
||||
|
||||
@@ -62,6 +62,7 @@ class CreateEventCommand {
|
||||
'vat_pricing_mode' => $tenant->vat_pricing_mode,
|
||||
'tax_exemption_reason' => $tenant->tax_exemption_reason,
|
||||
'tax_exemption_note' => $tenant->tax_exemption_note,
|
||||
'participant_group_type' => $this->request->participantGroupType->value,
|
||||
// Den Event-Teil der Rechnungsnummer einfrieren: Tenant-Präfix und Startdatum sind später
|
||||
// änderbar, eine herausgegebene Rechnung würde sonst zu einer anderen Nummer gehören.
|
||||
//
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Event\Actions\CreateEvent;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use DateTime;
|
||||
|
||||
@@ -21,8 +22,11 @@ class CreateEventRequest {
|
||||
public bool $payPerDay;
|
||||
public ?string $street;
|
||||
public ?string $houseNumber;
|
||||
/** Wonach bei der Anmeldung gefragt wird: Local Group oder Parent Group. */
|
||||
public ParticipantGroupType $participantGroupType;
|
||||
|
||||
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, ?string $street = null, ?string $houseNumber = null) {
|
||||
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, ?string $street = null, ?string $houseNumber = null, ParticipantGroupType $participantGroupType = ParticipantGroupType::LocalGroup) {
|
||||
$this->participantGroupType = $participantGroupType;
|
||||
$this->name = $name;
|
||||
$this->location = $location;
|
||||
$this->postalCode = $postalCode;
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Domains\Event\Actions\ShortSignUp;
|
||||
|
||||
use App\Domains\Event\Actions\SignUp\SignUpCommand;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpRequest;
|
||||
use App\Models\ParentGroup;
|
||||
use App\Models\Tenant;
|
||||
use App\ValueObjects\Age;
|
||||
|
||||
/**
|
||||
@@ -53,18 +55,21 @@ class ShortSignUpCommand {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Stamm: bei genau einem teilnehmenden Stamm wird dieser gesetzt, bei mehreren muss gewählt werden,
|
||||
// ohne teilnehmende Stämme bleibt die Zuordnung leer.
|
||||
$localGroups = $this->request->event->localGroups;
|
||||
$localGroup = null;
|
||||
// Gruppe (Local oder Parent Group, je nach Veranstaltung): bei genau einer Option wird diese gesetzt, bei
|
||||
// mehreren muss gewählt werden, ohne Optionen bleibt die Zuordnung leer.
|
||||
$groupOptions = $this->request->event->participantGroupOptions();
|
||||
$participantGroup = null;
|
||||
|
||||
if ($localGroups->count() === 1) {
|
||||
$localGroup = $localGroups->first();
|
||||
} elseif ($localGroups->count() > 1) {
|
||||
$localGroup = $localGroups->firstWhere('id', $this->request->localGroupId);
|
||||
if ($groupOptions->count() === 1) {
|
||||
$participantGroup = $groupOptions->first();
|
||||
} elseif ($groupOptions->count() > 1) {
|
||||
$participantGroup = $groupOptions->firstWhere('id', $this->request->localGroupId);
|
||||
|
||||
if ($localGroup === null) {
|
||||
$response->message = 'Bitte einen Stamm auswählen.';
|
||||
if ($participantGroup === null) {
|
||||
$response->message = sprintf(
|
||||
'Bitte bei „%s" eine Auswahl treffen.',
|
||||
$this->request->event->participantGroupType()->label(),
|
||||
);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +92,7 @@ class ShortSignUpCommand {
|
||||
lastname: $this->request->lastname,
|
||||
nickname: $this->request->nickname,
|
||||
participationType: $participationType,
|
||||
localGroup: $localGroup,
|
||||
localGroup: $participantGroup instanceof Tenant ? $participantGroup : null,
|
||||
birthday: $this->request->birthday,
|
||||
address_1: self::PLACEHOLDER,
|
||||
address_2: null,
|
||||
@@ -123,6 +128,7 @@ class ShortSignUpCommand {
|
||||
addonItems: [],
|
||||
feeType: $this->request->feeType,
|
||||
siblingReduction: false,
|
||||
parentGroup: $participantGroup instanceof ParentGroup ? $participantGroup : null,
|
||||
);
|
||||
|
||||
$signUpResponse = new SignUpCommand($signUpRequest)->execute();
|
||||
|
||||
@@ -61,6 +61,7 @@ class SignUpCommand {
|
||||
'fee_type' => $this->request->feeType,
|
||||
'sibling_reduction' => $this->request->siblingReduction,
|
||||
'local_group' => $this->request->localGroup?->slug,
|
||||
'parent_group_id' => $this->request->parentGroup?->id,
|
||||
'birthday' => $this->request->birthday,
|
||||
'address_1' => $this->request->address_1,
|
||||
'address_2' => $this->request->address_2,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Domains\Event\Actions\SignUp;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\ParentGroup;
|
||||
use App\Models\Tenant;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
@@ -55,6 +56,8 @@ class SignUpRequest {
|
||||
public ?string $feeType = null,
|
||||
/** Ob die 50-%-Geschwisterermäßigung in `amount` eingerechnet ist. */
|
||||
public bool $siblingReduction = false,
|
||||
/** Parent Group -- nur bei Veranstaltungen, die danach statt nach der Local Group fragen. */
|
||||
public ?ParentGroup $parentGroup = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
use App\Repositories\EventParticipantRepository;
|
||||
|
||||
class UpdateEventCommand {
|
||||
public UpdateEventRequest $request;
|
||||
public function __construct(UpdateEventRequest $request) {
|
||||
@@ -26,6 +28,14 @@ class UpdateEventCommand {
|
||||
$this->request->event->registration_allowed = $this->request->registrationAllowed;
|
||||
$this->request->event->short_registration = $this->request->shortRegistration;
|
||||
$this->request->event->swimming_permission_required = $this->request->swimmingPermissionRequired;
|
||||
|
||||
// Umstellen zwischen Local und Parent Group nur ohne Anmeldungen -- sonst stünden in einer Veranstaltung
|
||||
// beide Arten von Antworten nebeneinander.
|
||||
if ($this->request->participantGroupType !== null
|
||||
&& !new EventParticipantRepository()->existsForEvent($this->request->event)) {
|
||||
$this->request->event->participant_group_type = $this->request->participantGroupType->value;
|
||||
}
|
||||
|
||||
$this->request->event->save();
|
||||
|
||||
$this->request->event->resetAllowedEatingHabits();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Models\Event;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
@@ -25,9 +26,12 @@ class UpdateEventRequest {
|
||||
public Amount $supportPerPerson;
|
||||
public array $contributingLocalGroups;
|
||||
public array $eatingHabits;
|
||||
/** Wonach bei der Anmeldung gefragt wird; null = unverändert. Greift nur, solange es keine Anmeldungen gibt. */
|
||||
public ?ParticipantGroupType $participantGroupType;
|
||||
|
||||
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, bool $shortRegistration = false, bool $swimmingPermissionRequired = true, ?string $street = null, ?string $houseNumber = null)
|
||||
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, bool $shortRegistration = false, bool $swimmingPermissionRequired = true, ?string $street = null, ?string $houseNumber = null, ?ParticipantGroupType $participantGroupType = null)
|
||||
{
|
||||
$this->participantGroupType = $participantGroupType;
|
||||
$this->event = $event;
|
||||
$this->eventName = $eventName;
|
||||
$this->eventLocation = $eventLocation;
|
||||
|
||||
@@ -31,9 +31,8 @@ class UpdateParticipantCommand {
|
||||
$p->address_2 = $this->request->address_2;
|
||||
$p->postcode = $this->request->postcode;
|
||||
$p->city = $this->request->city;
|
||||
// Leerer String würde als Fremdschlüssel scheitern -- kein Stamm heißt null.
|
||||
$p->local_group = $this->request->localgroup ?: null;
|
||||
$p->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday);
|
||||
$this->applyParticipantGroup($p);
|
||||
$p->birthday = DateTime::createFromFormat('Y-m-d', $this->request->birthday);
|
||||
$p->email_1 = $this->request->email_1;
|
||||
$p->phone_1 = $this->request->phone_1;
|
||||
$p->contact_person = $this->request->contact_person;
|
||||
@@ -87,6 +86,29 @@ class UpdateParticipantCommand {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Übernimmt die gewählte Gruppe -- je nach Veranstaltung die Local Group (Slug) oder die Parent Group (ID).
|
||||
* Angenommen wird nur, was die Veranstaltung zur Auswahl stellt; leer heißt keine Angabe (null, ein leerer
|
||||
* String würde als Fremdschlüssel scheitern).
|
||||
*/
|
||||
private function applyParticipantGroup(EventParticipant $participant): void
|
||||
{
|
||||
$event = $participant->event;
|
||||
$value = trim((string) $this->request->participantGroup);
|
||||
|
||||
if ($event->asksForParentGroup()) {
|
||||
$participant->parent_group_id = $value === ''
|
||||
? null
|
||||
: $event->participantGroupOptions()->firstWhere('id', (int) $value)?->id;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$participant->local_group = $value === ''
|
||||
? null
|
||||
: $event->participantGroupOptions()->firstWhere('slug', $value)?->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ersetzt die zugeordneten Zusätze (Event-Addons) anhand der Auswahl. Bewusst **ohne Preis/Betrag**
|
||||
* (amount 0): der Teilnahmebeitrag wird in den Details ohnehin manuell gesetzt. Es wird nur der Titel als
|
||||
|
||||
@@ -14,8 +14,11 @@ class UpdateParticipantRequest {
|
||||
public ?string $address_2,
|
||||
public string $postcode,
|
||||
public string $city,
|
||||
/** Stamm-Slug; leer/null bei Anmeldungen, die keinen Stamm erfasst haben (Kurzanmeldung). */
|
||||
public ?string $localgroup,
|
||||
/**
|
||||
* Gewählte Gruppe: Slug der Local Group bzw. ID der Parent Group, je nachdem, wonach die Veranstaltung
|
||||
* fragt. Leer/null = keine Angabe.
|
||||
*/
|
||||
public ?string $participantGroup,
|
||||
public string $birthday,
|
||||
public string $email_1,
|
||||
public string $phone_1,
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Domains\Event\Actions\CreateEvent\CreateEventRequest;
|
||||
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitCommand;
|
||||
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\EventResource;
|
||||
@@ -28,6 +29,7 @@ class CreateController extends CommonController {
|
||||
'participationFeeType' => $this->tenant->slug === 'lv' ?
|
||||
ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED :
|
||||
ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY,
|
||||
'participantGroupTypes' => ParticipantGroupType::options(),
|
||||
])->render();
|
||||
}
|
||||
|
||||
@@ -58,7 +60,8 @@ class CreateController extends CommonController {
|
||||
$request->input('eventIban'),
|
||||
$payPerDay,
|
||||
$request->input('eventStreet'),
|
||||
$request->input('eventHouseNumber')
|
||||
$request->input('eventHouseNumber'),
|
||||
ParticipantGroupType::fromStored($request->input('eventParticipantGroupType')),
|
||||
);
|
||||
|
||||
$wasSuccessful = false;
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Domains\Event\Actions\UpdateEvent\UpdateEventCommand;
|
||||
use App\Domains\Event\Actions\UpdateEvent\UpdateEventRequest;
|
||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
|
||||
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
|
||||
use App\Enumerations\ParticipantGroupType;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Providers\InertiaProvider;
|
||||
@@ -27,7 +28,10 @@ use Illuminate\Http\Response;
|
||||
class DetailsController extends CommonController {
|
||||
public function __invoke(string $eventId) {
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
||||
return new InertiaProvider('Event/Details', [
|
||||
'event' => $event,
|
||||
'participantGroupLabel' => $event->participantGroupType()->label(),
|
||||
])->render();
|
||||
}
|
||||
|
||||
public function summary(int $eventId, Request $request) : JsonResponse {
|
||||
@@ -65,6 +69,7 @@ class DetailsController extends CommonController {
|
||||
(bool)$request->input('swimmingPermissionRequired', true),
|
||||
$request->input('street'),
|
||||
$request->input('houseNumber'),
|
||||
ParticipantGroupType::tryFrom((string) $request->input('participantGroupType')),
|
||||
);
|
||||
|
||||
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
||||
@@ -185,6 +190,7 @@ class DetailsController extends CommonController {
|
||||
'rows' => $participants,
|
||||
'kitchenRequirements' => $kitchenOverview,
|
||||
'participantsForKitchenList' => $this->eventParticipants->getParticipantsWithIntolerances($event, $request),
|
||||
'participantGroupLabel' => $event->participantGroupType()->label(),
|
||||
])->render();
|
||||
|
||||
$pdf = PdfGenerateAndDownloadProvider::fromHtml(
|
||||
@@ -208,6 +214,9 @@ class DetailsController extends CommonController {
|
||||
$csv = view('csvs.' . $listType, [
|
||||
'event' => $event->name,
|
||||
'rows' => $participants,
|
||||
// Die Anschriftsspalten beschreiben immer die Local Group, die Gruppenspalte die gewählte Gruppe.
|
||||
'localGroupLabel' => ParticipantGroupType::LocalGroup->label(),
|
||||
'participantGroupLabel' => $event->participantGroupType()->label(),
|
||||
])->render();
|
||||
|
||||
return response($csv, 200, [
|
||||
@@ -220,7 +229,7 @@ class DetailsController extends CommonController {
|
||||
$event = $this->events->getByIdentifier($eventId);
|
||||
switch ($listType) {
|
||||
case 'by-local-group':
|
||||
$participants = $this->eventParticipants->groupByLocalGroup($event, $request);
|
||||
$participants = $this->eventParticipants->groupByParticipantGroup($event, $request);
|
||||
break;
|
||||
case 'by-participation-group':
|
||||
$participants = $this->eventParticipants->groupByParticipationType($event, $request);
|
||||
|
||||
@@ -12,7 +12,7 @@ class ByGroupController extends CommonController
|
||||
$recipients = [];
|
||||
switch ($groupType) {
|
||||
case 'by-local-group':
|
||||
$participants = $this->eventParticipants->groupByLocalGroup($event, $request, $request->input('groupName'));
|
||||
$participants = $this->eventParticipants->groupByParticipantGroup($event, $request, $request->input('groupName'));
|
||||
$recipients = $this->eventParticipants->getMailAddresses($participants[$request->input('groupName')]);
|
||||
break;
|
||||
case 'by-participation-group':
|
||||
|
||||
@@ -22,7 +22,7 @@ class ParticipantUpdateController extends CommonController {
|
||||
address_2: $request->input('address_2'),
|
||||
postcode: $request->input('postcode'),
|
||||
city: $request->input('city'),
|
||||
localgroup: $request->input('localgroup'),
|
||||
participantGroup: $request->input('participantGroup'),
|
||||
birthday: $request->input('birthday'),
|
||||
email_1: $request->input('email_1'),
|
||||
phone_1: $request->input('phone_1'),
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConducti
|
||||
use App\Domains\Event\Actions\SignUp\SignUpCommand;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpRequest;
|
||||
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
||||
use App\Models\ParentGroup;
|
||||
use App\Models\Tenant;
|
||||
use App\Providers\DoubleCheckEventRegistrationProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
@@ -65,7 +66,7 @@ class SignupController extends CommonController {
|
||||
$inertiaProvider = new InertiaProvider('Event/Signup', [
|
||||
'event' => $event,
|
||||
'availableEvents' => $availableEvents,
|
||||
'localGroups' => $event['contributingLocalGroups'],
|
||||
'localGroups' => $event['participantGroupOptions'] ?? [],
|
||||
'participantData' => $participantData,
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
@@ -111,6 +112,15 @@ class SignupController extends CommonController {
|
||||
$addonResolution = $eventResource->resolveAddons($selectedAddonKeys, $arrival, $departure);
|
||||
$amount->addAmount($addonResolution['total']);
|
||||
|
||||
// Gewählt wird eine der Optionen der Veranstaltung (Local oder Parent Group); anderes wird abgelehnt.
|
||||
$participantGroup = $event->participantGroupOptions()->firstWhere('id', (int)($registrationData['localGroup'] ?? 0));
|
||||
if ($participantGroup === null) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => sprintf('Bitte bei „%s" eine Auswahl treffen.', $event->participantGroupType()->label()),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$signupRequest = new SignUpRequest(
|
||||
$event,
|
||||
$registrationData['userId'] ?? null,
|
||||
@@ -118,7 +128,7 @@ class SignupController extends CommonController {
|
||||
$registrationData['nachname'],
|
||||
$registrationData['pfadiname'],
|
||||
$registrationData['participationType'],
|
||||
Tenant::findOrFail($registrationData['localGroup']),
|
||||
$participantGroup instanceof Tenant ? $participantGroup : null,
|
||||
\DateTime::createFromFormat('Y-m-d', $registrationData['geburtsdatum']),
|
||||
$registrationData['address1'],
|
||||
$registrationData['address2'],
|
||||
@@ -153,6 +163,7 @@ class SignupController extends CommonController {
|
||||
$addonResolution['items'],
|
||||
$registrationData['beitrag'],
|
||||
$siblingReduction,
|
||||
$participantGroup instanceof ParentGroup ? $participantGroup : null,
|
||||
);
|
||||
|
||||
$signupCommand = new SignUpCommand($signupRequest);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
'eventIban': String,
|
||||
"eventPayPerDay": Boolean,
|
||||
"participationFeeType": String,
|
||||
"participantGroupTypes": {type: Array, default: () => []},
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
@@ -35,6 +36,7 @@
|
||||
eventIban: props.eventIban ? props.eventIban : '',
|
||||
eventPayPerDay: props.eventPayPerDay ? props.eventPayPerDay : false,
|
||||
eventParticipationFeeType: props.participationFeeType ? props.participationFeeType : 'fixed',
|
||||
eventParticipantGroupType: 'local_group',
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -159,6 +161,7 @@
|
||||
eventIban: formData.eventIban,
|
||||
eventPayPerDay: formData.eventPayPerDay,
|
||||
eventParticipationFeeType: formData.eventParticipationFeeType,
|
||||
eventParticipantGroupType: formData.eventParticipantGroupType,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -256,6 +259,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Bei der Anmeldung abfragen</th>
|
||||
<td class="height-50">
|
||||
<select v-model="formData.eventParticipantGroupType" class="width-half-full">
|
||||
<option v-for="type in props.participantGroupTypes" :key="type.value" :value="type.value">{{ type.label }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr style="vertical-align: top;">
|
||||
<th class="width-medium pr-20 height-50">Preiserhöhung nach Early-Bird-Phase</th>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Overview from "./Partials/Overview.vue";
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
participantGroupLabel: {type: String, default: 'Stamm'},
|
||||
})
|
||||
|
||||
const tabs = [
|
||||
@@ -24,7 +25,7 @@ const tabs = [
|
||||
endpoint: "/api/v1/event/details/" + props.event.identifier + '/participants/all',
|
||||
},
|
||||
{
|
||||
title: 'Teilis nach Stamm',
|
||||
title: 'Teilis nach ' + props.participantGroupLabel,
|
||||
component: ParticipantsList,
|
||||
endpoint: "/api/v1/event/details/" + props.event.identifier + '/participants/by-local-group',
|
||||
},
|
||||
|
||||
@@ -16,8 +16,13 @@ const emit = defineEmits(['close'])
|
||||
const dynmicProps = reactive({
|
||||
localGroups: [],
|
||||
eatingHabits:[],
|
||||
participantGroupTypes: [],
|
||||
localGroupLabelPlural: 'Stämme',
|
||||
});
|
||||
|
||||
// Umstellen zwischen Local und Parent Group nur, solange es keine Anmeldungen gibt.
|
||||
const participantGroupTypeLocked = (props.event.totalParticipantCount ?? 0) > 0
|
||||
|
||||
const contributingLocalGroups = ref([])
|
||||
const eatingHabits = ref([]);
|
||||
|
||||
@@ -41,6 +46,7 @@ const emit = defineEmits(['close'])
|
||||
swimmingPermissionRequired: props.event.swimmingPermissionRequired,
|
||||
flatSupport: props.event.flatSupportEdit,
|
||||
supportPerson: props.event.supportPersonIndex,
|
||||
participantGroupType: props.event.participantGroupType ?? 'local_group',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -76,6 +82,7 @@ const emit = defineEmits(['close'])
|
||||
supportPerson: formData.supportPerson,
|
||||
contributingLocalGroups: contributingLocalGroups.value,
|
||||
eatingHabits: eatingHabits.value,
|
||||
participantGroupType: formData.participantGroupType,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -173,6 +180,18 @@ const emit = defineEmits(['close'])
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Bei der Anmeldung abfragen</th>
|
||||
<td>
|
||||
<select v-model="formData.participantGroupType" class="width-full" :disabled="participantGroupTypeLocked">
|
||||
<option v-for="type in dynmicProps.participantGroupTypes" :key="type.value" :value="type.value">{{ type.label }}</option>
|
||||
</select>
|
||||
<span v-if="participantGroupTypeLocked" style="font-size: 0.8rem; color: #6b7280;">
|
||||
Nicht mehr änderbar, da bereits Anmeldungen vorliegen.
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Mindestalter für Alkoholkonsum</th>
|
||||
<td>
|
||||
@@ -223,7 +242,7 @@ const emit = defineEmits(['close'])
|
||||
<div class="right">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Teilnehmende Stämme</th>
|
||||
<th>Teilnehmende {{ dynmicProps.localGroupLabelPlural }}</th>
|
||||
</tr>
|
||||
<tr v-for="localGroup in dynmicProps.localGroups">
|
||||
<td>
|
||||
|
||||
@@ -42,7 +42,7 @@ const form = reactive({
|
||||
address_2: '',
|
||||
postcode: '',
|
||||
city: '',
|
||||
localgroup: '',
|
||||
participantGroup: '',
|
||||
birthday: '',
|
||||
email_1: '',
|
||||
phone_1: '',
|
||||
@@ -71,6 +71,9 @@ const form = reactive({
|
||||
// Teilnahmeoptionen des Events (Definition mit Titel/Frage/Optionen).
|
||||
const participationQuestions = computed(() => staticProps.event?.participationOptions ?? []);
|
||||
|
||||
// Bezeichnung der gewählten Gruppe (z. B. „Stamm" oder „Bund"), je nach Einstellung der Veranstaltung.
|
||||
const localGroupLabel = computed(() => staticProps.event?.participantGroupLabel ?? 'Stamm');
|
||||
|
||||
// Zusatzoptionen (Event-Addons) des Events.
|
||||
const eventAddons = computed(() => staticProps.event?.addons ?? []);
|
||||
|
||||
@@ -95,7 +98,7 @@ watch(
|
||||
form.address_2 = participant?.address_2 ?? '';
|
||||
form.postcode = participant?.postcode ?? '';
|
||||
form.city = participant?.city ?? '';
|
||||
form.localgroup = participant?.local_group ?? '';
|
||||
form.participantGroup = participant?.participantGroupValue ?? '';
|
||||
form.birthday = participant?.birthdayDate ?? '';
|
||||
form.email_1 = participant?.email_1 ?? '';
|
||||
form.phone_1 = participant?.phone_1 ?? '';
|
||||
@@ -229,12 +232,12 @@ function saveParticipant() {
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Stamm</th>
|
||||
<th>{{ localGroupLabel }}</th>
|
||||
<td>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.localgroup }}</span>
|
||||
<select v-else v-model="form.localgroup">
|
||||
<option value="">Kein Stamm</option>
|
||||
<option v-for="group in staticProps.event.contributingLocalGroups" :key="group.id" :value="group.slug">{{ group.name }}</option>
|
||||
<span v-if="!staticProps.editMode">{{ props.participant.participantGroup }}</span>
|
||||
<select v-else v-model="form.participantGroup">
|
||||
<option value="">Keine Angabe</option>
|
||||
<option v-for="group in staticProps.event.participantGroupOptions" :key="group.id" :value="group.value">{{ group.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -162,7 +162,7 @@ async function saveParticipant(formData) {
|
||||
document.getElementById('participant-' + data.participant.identifier + '-fullname').innerHTML = data.participant.fullname;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-birthday').innerText = data.participant.birthday;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-age').innerText = data.participant.age;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-localgroup').innerText = data.participant.localgroup;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-localgroup').innerText = data.participant.participantGroup;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-arrival').innerText = data.participant.arrival;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-departure').innerText = data.participant.departure;
|
||||
document.getElementById('participant-' + data.participant.identifier + '-email_1').innerText = data.participant.email_1;
|
||||
@@ -641,7 +641,7 @@ function mailToGroup(groupKey) {
|
||||
<tr class="participant-meta-row" :id="'participant-' + participant.identifier + '-meta'">
|
||||
<td colspan="5" class="pl-meta">
|
||||
<label :id="'participant-' + participant.identifier +'-localgroup'">
|
||||
{{ participant?.localgroup ?? "-" }}
|
||||
{{ participant?.participantGroup ?? "-" }}
|
||||
</label> |
|
||||
<strong> Anreise: </strong>
|
||||
<label :id="'participant-' + participant.identifier +'-arrival'">
|
||||
|
||||
@@ -13,10 +13,12 @@ export function useShortSignupForm(event, participantData) {
|
||||
// Gibt es nur eine Zahlungsart, wird sie vorausgewählt und der Zahlungsschritt entfällt.
|
||||
const activeMethods = (event.paymentMethods ?? []).filter(m => m.active)
|
||||
|
||||
// Bei genau einem teilnehmenden Stamm wird dieser gesetzt, ohne danach zu fragen; erst ab zwei
|
||||
// Stämmen erscheint das Auswahlfeld.
|
||||
const localGroups = event.contributingLocalGroups ?? []
|
||||
// Auswahl je nach Veranstaltung: teilnehmende Stämme oder deren Bünde. Bei genau einer Option wird diese
|
||||
// gesetzt, ohne danach zu fragen; erst ab zwei Optionen erscheint das Auswahlfeld.
|
||||
const localGroups = event.participantGroupOptions ?? []
|
||||
const needsLocalGroup = localGroups.length > 1
|
||||
// Vorbelegung mit dem Stamm des eingeloggten Nutzers nur, wenn er zur Auswahl gehört (bei Bund-Abfrage nie).
|
||||
const preselectedGroup = localGroups.some(g => g.id === participantData.localGroup) ? participantData.localGroup : '-1'
|
||||
|
||||
const formData = reactive({
|
||||
userId: participantData.id,
|
||||
@@ -26,7 +28,7 @@ export function useShortSignupForm(event, participantData) {
|
||||
geburtsdatum: participantData.birthday ?? '',
|
||||
email_1: participantData.email ?? '',
|
||||
telefon_1: participantData.phone ?? '',
|
||||
localGroup: localGroups.length === 1 ? localGroups[0].id : (participantData.localGroup ?? '-1'),
|
||||
localGroup: localGroups.length === 1 ? localGroups[0].id : preselectedGroup,
|
||||
ansprechpartner: '',
|
||||
email_2: '',
|
||||
allergien: participantData.allergies ?? '',
|
||||
|
||||
@@ -11,6 +11,9 @@ const props = defineProps({
|
||||
})
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
// Bezeichnung der Auswahl (z. B. „Stamm" oder „Bund"), je nach Einstellung der Veranstaltung.
|
||||
const localGroupLabel = props.event?.participantGroupLabel ?? 'Stamm'
|
||||
|
||||
const errors = reactive({
|
||||
vorname: '',
|
||||
nachname: '',
|
||||
@@ -46,7 +49,7 @@ const next = () => {
|
||||
}
|
||||
|
||||
if (props.needsLocalGroup && (!props.formData.localGroup || props.formData.localGroup === '-1')) {
|
||||
errors.localGroup = 'Bitte einen Stamm auswählen.'
|
||||
errors.localGroup = 'Bitte bei „' + localGroupLabel + '" eine Auswahl treffen.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
@@ -122,7 +125,7 @@ const next = () => {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="needsLocalGroup">
|
||||
<td>Stamm:</td>
|
||||
<td>{{ localGroupLabel }}:</td>
|
||||
<td>
|
||||
<select v-model="formData.localGroup">
|
||||
<option value="-1">Bitte wählen</option>
|
||||
|
||||
@@ -43,9 +43,9 @@ const selectedParticipationOptions = computed(() =>
|
||||
}).filter(entry => entry.value !== '')
|
||||
)
|
||||
|
||||
// Der Stamm steht entweder fest (genau ein teilnehmender Stamm) oder wurde auf Seite 1 gewählt.
|
||||
// Die Gruppe (Stamm oder Bund) steht entweder fest (genau eine Option) oder wurde auf Seite 1 gewählt.
|
||||
const localGroupName = computed(() =>
|
||||
(props.event.contributingLocalGroups ?? []).find(g => g.id === props.formData.localGroup)?.name ?? null
|
||||
(props.event.participantGroupOptions ?? []).find(g => g.id === props.formData.localGroup)?.name ?? null
|
||||
)
|
||||
|
||||
const permissionName = (list, slug) =>
|
||||
@@ -84,7 +84,7 @@ const canSubmit = computed(() =>
|
||||
<td>{{ formData.telefon_1 }}</td>
|
||||
</tr>
|
||||
<tr v-if="localGroupName">
|
||||
<td>Stamm:</td>
|
||||
<td>{{ event.participantGroupLabel ?? 'Stamm' }}:</td>
|
||||
<td>{{ localGroupName }}</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ const steps = [
|
||||
<form @submit.prevent="submit">
|
||||
<StepAge v-if="currentStep === 1" :event="event" @next="goToStep" />
|
||||
<StepContactPerson v-if="currentStep === 2" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepPersonalData v-if="currentStep === 3" :formData="formData" :localGroups="localGroups" @next="goToStep" @back="goToStep" />
|
||||
<StepPersonalData v-if="currentStep === 3" :formData="formData" :event="event" :localGroups="localGroups" @next="goToStep" @back="goToStep" />
|
||||
<StepArrival v-if="currentStep === 4" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepRegistrationMode v-if="currentStep === 5" :formData="formData" :event="event" @next="goToStep" @back="goToStep" />
|
||||
<StepAddons v-if="currentStep === 6" :formData="formData" :event="event" :selectedAddons="selectedAddons" @next="goToStep" @back="goToStep" />
|
||||
|
||||
@@ -24,7 +24,10 @@ export function useSignupForm(event, participantData) {
|
||||
vorname: participantData.firstname ?? '',
|
||||
nachname: participantData.lastname ?? '',
|
||||
pfadiname: participantData.nickname ?? '',
|
||||
localGroup: participantData.localGroup ?? '-1',
|
||||
// Vorbelegung mit dem Stamm des eingeloggten Nutzers nur, wenn er zur Auswahl gehört (bei Bund-Abfrage nie).
|
||||
localGroup: (event.participantGroupOptions ?? []).some(g => g.id === participantData.localGroup)
|
||||
? participantData.localGroup
|
||||
: '-1',
|
||||
geburtsdatum: participantData.birthday ?? '',
|
||||
address1: participantData.address_1 ?? '',
|
||||
address2: participantData.address_2 ?? '',
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
import {reactive} from "vue";
|
||||
import ErrorText from "../../../../../../Views/Components/ErrorText.vue";
|
||||
|
||||
const props = defineProps({ formData: Object, localGroups: Array })
|
||||
const props = defineProps({ formData: Object, event: Object, localGroups: Array })
|
||||
const emit = defineEmits(['next', 'back'])
|
||||
|
||||
// Bezeichnung der Auswahl (z. B. „Stamm" oder „Bund"), je nach Einstellung der Veranstaltung.
|
||||
const localGroupLabel = props.event?.participantGroupLabel ?? 'Stamm'
|
||||
|
||||
const errors = reactive({
|
||||
vorname: '',
|
||||
nachname: '',
|
||||
@@ -46,7 +49,7 @@ const next = () => {
|
||||
}
|
||||
|
||||
if (props.formData.localGroup === '-1') {
|
||||
errors.localGroup = 'Bitte den Stamm auswählen.'
|
||||
errors.localGroup = 'Bitte bei „' + localGroupLabel + '" eine Auswahl treffen.'
|
||||
hasError = true
|
||||
}
|
||||
|
||||
@@ -90,7 +93,7 @@ const next = () => {
|
||||
<tr><td>Nachname:</td><td><input type="text" v-model="props.formData.nachname" /><ErrorText :message="errors.nachname"/></td></tr>
|
||||
<tr><td>Pfadiname:</td><td><input type="text" v-model="props.formData.pfadiname" /></td></tr>
|
||||
<tr>
|
||||
<td>Stamm:</td>
|
||||
<td>{{ localGroupLabel }}:</td>
|
||||
<td>
|
||||
<select v-model="props.formData.localGroup">
|
||||
<option value="-1">Bitte wählen</option>
|
||||
|
||||
Reference in New Issue
Block a user