Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28ffbdb696 | |||
| 575fb27018 | |||
| fe3429cd4e | |||
| 551b592b3b | |||
| 6ed0a5b93a | |||
| 97fd7cd0da | |||
| f5d7b21671 | |||
| 444711b049 | |||
| 0a7abb1389 | |||
| 998a799c3a | |||
| ef8f3ebe6c | |||
| 92976fbf27 | |||
| e7e7f039b8 | |||
| 0d436d8190 | |||
| 3fdbaf0285 | |||
| 8a049efe49 | |||
| a62b2214c4 | |||
| f00e665a03 | |||
| 454d83de2e | |||
| 30cc0b79c5 | |||
| 775d9158a6 | |||
| c60429ad28 | |||
| 0cf9602958 | |||
| e2fb616565 | |||
| faaf12b6a4 | |||
| 1a69a20750 | |||
| 71cb09f051 | |||
| b668195e21 | |||
| 94934027fa | |||
| 120c8873ef | |||
| 188735e4aa | |||
| b291c05da7 | |||
| adffa6d6b2 | |||
| 4357fd022d | |||
| d6416d9c6b | |||
| 7c1125e213 | |||
| b91af2e889 | |||
| 4a97cf230b | |||
| 0c7e3ab365 | |||
| 222113b0d5 | |||
| 895e44c984 | |||
| ef3d03200e | |||
| 6c891ff64a | |||
| d9ba5ab4f1 | |||
| 1816ca5165 | |||
| dff7d65476 | |||
| ce57669263 | |||
| 2444aab204 | |||
| 7b2f2de1f0 |
@@ -0,0 +1,5 @@
|
|||||||
|
.ai
|
||||||
|
.junie
|
||||||
|
.git
|
||||||
|
storage
|
||||||
|
tests
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Actions\CreateEstimate;
|
||||||
|
|
||||||
|
use App\Models\CostUnitEstimate;
|
||||||
|
|
||||||
|
class CreateEstimateAction {
|
||||||
|
private CreateEstimateResponse $response;
|
||||||
|
|
||||||
|
public function __construct(private CreateEstimateRequest $request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): CreateEstimateResponse {
|
||||||
|
$this->response = new CreateEstimateResponse();
|
||||||
|
|
||||||
|
$amount = [];
|
||||||
|
switch ($this->request->amountType) {
|
||||||
|
case 'flat':
|
||||||
|
$amount['flat_amount'] = $this->request->amount;
|
||||||
|
break;
|
||||||
|
case 'per_person':
|
||||||
|
$amount['amount_by_user'] = $this->request->amount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->estimateId === 0) {
|
||||||
|
$estimate = CostUnitEstimate::create(array_merge([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'cost_unit_id' => $this->request->costUnit->id,
|
||||||
|
'type' => $this->request->estimateType,
|
||||||
|
'description' => $this->request->description,
|
||||||
|
], $amount));
|
||||||
|
} else {
|
||||||
|
$estimate = CostUnitEstimate::find($this->request->estimateId);
|
||||||
|
$estimate->update(array_merge([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'cost_unit_id' => $this->request->costUnit->id,
|
||||||
|
'type' => $this->request->estimateType,
|
||||||
|
'description' => $this->request->description,
|
||||||
|
], $amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($estimate !== null) {
|
||||||
|
$this->response->estimateId = $estimate->id;
|
||||||
|
$this->response->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Actions\CreateEstimate;
|
||||||
|
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Models\CostUnit;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
|
class CreateEstimateRequest {
|
||||||
|
function __construct(
|
||||||
|
public string $amountType,
|
||||||
|
public string $description,
|
||||||
|
public Amount $amount,
|
||||||
|
public string $estimateType,
|
||||||
|
public CostUnit $costUnit,
|
||||||
|
public int $estimateId,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Actions\CreateEstimate;
|
||||||
|
|
||||||
|
class CreateEstimateResponse {
|
||||||
|
public bool $success;
|
||||||
|
public ?int $estimateId;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
$this->estimateId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Actions\DeleteEstimate;
|
||||||
|
|
||||||
|
class DeleteEstimateAction {
|
||||||
|
public function __construct(private DeleteEstimateRequest $request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : DeleteEstimateResponse {
|
||||||
|
$response = new DeleteEstimateResponse();
|
||||||
|
$this->request->estimate->delete();
|
||||||
|
$response->success = true;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Actions\DeleteEstimate;
|
||||||
|
|
||||||
|
use App\Models\CostUnitEstimate;
|
||||||
|
|
||||||
|
class DeleteEstimateRequest {
|
||||||
|
public function __construct(public CostUnitEstimate $estimate)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Actions\DeleteEstimate;
|
||||||
|
|
||||||
|
class DeleteEstimateResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateAction;
|
||||||
|
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateRequest;
|
||||||
|
use App\Domains\Budget\Actions\DeleteEstimate\DeleteEstimateAction;
|
||||||
|
use App\Domains\Budget\Actions\DeleteEstimate\DeleteEstimateRequest;
|
||||||
|
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class DeleteController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $costUnitId, int $estimateId, Request $request) : JsonResponse {
|
||||||
|
$estimate = $this->estimates->getById($estimateId);
|
||||||
|
|
||||||
|
if ($estimate === null) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Estimate not found'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deleteEstimateResponse =
|
||||||
|
new DeleteEstimateAction(request: new DeleteEstimateRequest($estimate)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
if ($deleteEstimateResponse->success) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Der Eintrag wurde erfolgreich gelöscht.'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Beim Löschen des Eintrags ist ein Fehler aufgetreten.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Controllers;
|
||||||
|
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ListController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $costUnitId, string $estimateType, Request $request) : JsonResponse {
|
||||||
|
$costUnit = $this->costUnits->getById($costUnitId);
|
||||||
|
$estimates = $this->estimates->getEstimates($costUnit, $estimateType);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'costUnitId' => $costUnitId,
|
||||||
|
'title' => InvoiceType::where('slug', $estimateType)->first()->name,
|
||||||
|
'estimateType' => $estimateType,
|
||||||
|
'estimates' => $estimates,
|
||||||
|
'totalAmountString' => $this->estimates->getTotalAmount($costUnit, $estimateType)->toString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class MainController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $costUnitId, Request $request) : Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Budget/List', [
|
||||||
|
'cost_unit_id' => $costUnitId
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Budget\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateAction;
|
||||||
|
use App\Domains\Budget\Actions\CreateEstimate\CreateEstimateRequest;
|
||||||
|
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SaveController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $costUnitId, Request $request) : JsonResponse {
|
||||||
|
$costUnit = $this->costUnits->getById($costUnitId);
|
||||||
|
|
||||||
|
if ($costUnit === null) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Cost unit not found'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$createCostUniResponse =
|
||||||
|
new CreateEstimateAction(request: new CreateEstimateRequest(
|
||||||
|
description: $request->input('description'),
|
||||||
|
amount: Amount::fromString($request->input('amount')),
|
||||||
|
amountType: $request->input('amount_type'),
|
||||||
|
estimateType: $request->input('estimateType'),
|
||||||
|
costUnit: $costUnit,
|
||||||
|
estimateId: $request->input('estimateId'),
|
||||||
|
))->execute();
|
||||||
|
|
||||||
|
if ($createCostUniResponse->success) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Der Eintrag wurde erfolgreich angelegt.'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Beim Anlegen des Eintrags ist ein Fehler aufgetreten.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Budget\Controllers\DeleteController;
|
||||||
|
use App\Domains\Budget\Controllers\SaveController;
|
||||||
|
use App\Domains\Budget\Controllers\ListController;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('api/v1')->group(function () {
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::prefix('budget')->group(function () {
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::prefix('/{costUnitId}')->group(function () {
|
||||||
|
Route::get('/list/{estimateType}', ListController::class);
|
||||||
|
Route::get('{estimateId}/delete', DeleteController::class);
|
||||||
|
Route::post('/save-estimate', SaveController::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Budget\Controllers\MainController;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::prefix('budget')->group(function () {
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::prefix('/{costUnitId}')->group(function() {
|
||||||
|
Route::get('/', MainController::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup>
|
||||||
|
import Modal from "../../../Views/Components/Modal.vue";
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import AmountInput from "../../../Views/Components/AmountInput.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
showAddEstimate: Boolean,
|
||||||
|
type: String,
|
||||||
|
title: String,
|
||||||
|
costUnitId: Number,
|
||||||
|
amount: Number,
|
||||||
|
amount_type: String,
|
||||||
|
estimateId: Number,
|
||||||
|
description: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(props)
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
amount_type: props.amount_type,
|
||||||
|
amount: props.amount,
|
||||||
|
description: props.description,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const data = await request('/api/v1/budget/' + props.costUnitId + '/save-estimate', {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
estimateId: props.estimateId,
|
||||||
|
amount_type: form.amount_type,
|
||||||
|
amount: form.amount,
|
||||||
|
description: form.description,
|
||||||
|
estimateType: props.type,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
toast.success(data.message);
|
||||||
|
} else {
|
||||||
|
toast.error(data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('closeAddEstimate')
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['closeAddEstimate'])
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
:show="showAddEstimate"
|
||||||
|
@close="emit('closeAddEstimate')"
|
||||||
|
title="Ausgabenschätzung hinzufügen"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Kostenstelle</th>
|
||||||
|
<td>{{title}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Verwendungszweck</th>
|
||||||
|
<td><input type="text" v-model="form.description" style="width: 250px;" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Betrag</th>
|
||||||
|
<td><AmountInput v-model="form.amount" style="width: 100px;" /> Euro</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Kostentyp</th>
|
||||||
|
<td style="vertical-align: top;">
|
||||||
|
<input type="radio" v-model="form.amount_type" value="flat"
|
||||||
|
id="amount_type_flat" />
|
||||||
|
<label for="amount_type_flat">Pauschal</label><br />
|
||||||
|
|
||||||
|
<input type="radio" v-model="form.amount_type" value="per_person" id="amount_type_per_person" />
|
||||||
|
<label for="amount_type_per_person">Pro Person</label><br />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="button" value="Speichern" class="button" @click="save" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<script setup>
|
||||||
|
import {reactive, inject, onMounted} from 'vue';
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||||
|
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
|
import ListBudgets from "./ListBudgetTypes.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
message: String,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
type: [Array, Object],
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
cost_unit_id: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Prüfen, ob ein ?id= Parameter in der URL übergeben wurde
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
const initialCostUnitId = props.cost_unit_id
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: 'Verpflegung',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/catering",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Unterkunft',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/accommodation",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Programm',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/program",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Logistik',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/logistic",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Technik',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/technical",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Reisekosten',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/travelling",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Verwaltung',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/management",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sonstiges',
|
||||||
|
component: ListBudgets,
|
||||||
|
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/other",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.message) {
|
||||||
|
toast.success(props.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title="Veranstaltungsbudget">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" />
|
||||||
|
|
||||||
|
</shadowed-box>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<script setup>
|
||||||
|
import {createApp, ref} from 'vue'
|
||||||
|
import LoadingModal from "../../../Views/Components/LoadingModal.vue";
|
||||||
|
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import AddOrUpdateEstimate from "./AddOrUpdateEstimate.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: [Array, Object],
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const localData = ref(props.data)
|
||||||
|
|
||||||
|
const showAddEstimate = ref(false)
|
||||||
|
const estimateId = ref(null)
|
||||||
|
const description = ref(null)
|
||||||
|
const amount = ref(null)
|
||||||
|
const amountType = ref(null)
|
||||||
|
|
||||||
|
const { data, loading, error, request, download } = useAjax()
|
||||||
|
|
||||||
|
async function reload() {
|
||||||
|
const url = "/api/v1/budget/" + props.data.costUnitId + "/list/" + props.data.estimateType
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { method: 'GET' })
|
||||||
|
if (!response.ok) throw new Error('Fehler beim Laden')
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
localData.value = result
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching estimates:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openAddEstimate() {
|
||||||
|
estimateId.value = 0
|
||||||
|
amount.value = 0.00
|
||||||
|
amountType.value = 'flat'
|
||||||
|
description.value = ''
|
||||||
|
showAddEstimate.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openEditEstimate(localEstimateId, localDescription, localAmount, localAmountType, localEstimateType) {
|
||||||
|
estimateId.value = localEstimateId
|
||||||
|
description.value = localDescription
|
||||||
|
amount.value = localAmount
|
||||||
|
amountType.value = localAmountType
|
||||||
|
console.log(localEstimateId, localDescription, localAmount, localAmountType, localEstimateType)
|
||||||
|
console.log(estimateId.value, description.value, amount.value, amountType.value, localEstimateType)
|
||||||
|
showAddEstimate.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteEstimate(currentEstimateId) {
|
||||||
|
const data = await request('/api/v1/budget/' + props.data.costUnitId + '/' + currentEstimateId + '/delete', {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
toast.success(data.message);
|
||||||
|
reload()
|
||||||
|
} else {
|
||||||
|
toast.error(data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="localData.estimates && localData.estimates.length > 0">
|
||||||
|
<h2>{{ props.data.title }}</h2>
|
||||||
|
<h3>Gesamtkosten: {{ localData.totalAmountString }}</h3>
|
||||||
|
<span v-for="estimate in localData.estimates">
|
||||||
|
<table style="width: 100%;">
|
||||||
|
<tr><th style="width: 200px;">
|
||||||
|
{{ estimate.title }}
|
||||||
|
</th>
|
||||||
|
<td>{{ estimate.singleAmountString }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td style="padding-bottom: 30px">
|
||||||
|
<label class="link" style="font-size: 10pt; margin-right: 20px;" @click="openEditEstimate(estimate.id, estimate.title, estimate.amountValue, estimate.amountType, props.data.estimateType)">Bearbeiten</label>
|
||||||
|
<label class="link" style="font-size: 10pt; margin-right: 20px; color: #ff0000;" @click="deleteEstimate(estimate.id)">Löschen</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<strong style="width: 100%; text-align: center; display: block; margin-top: 20px;">
|
||||||
|
Noch keine geschätzten Ausgaben vorhanden
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<label class="link" @click="openAddEstimate()">
|
||||||
|
Hinzufügen
|
||||||
|
</label>
|
||||||
|
<LoadingModal :show="showLoading" />
|
||||||
|
<AddOrUpdateEstimate
|
||||||
|
:amount="amount"
|
||||||
|
:amount_type="amountType"
|
||||||
|
:description="description"
|
||||||
|
:estimateId="estimateId"
|
||||||
|
:costUnitId="props.data.costUnitId"
|
||||||
|
:title="props.data.title"
|
||||||
|
:type="props.data.estimateType"
|
||||||
|
:showAddEstimate="showAddEstimate"
|
||||||
|
|
||||||
|
v-if="showAddEstimate"
|
||||||
|
@closeAddEstimate="showAddEstimate = false; reload()" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.costunit-list {
|
||||||
|
width: 96% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import InvoiceDetails from "../../../Invoice/Views/Partials/invoiceDetails/InvoiceDetails.vue";
|
import InvoiceDetails from "../../../Invoice/Views/Partials/invoiceDetails/InvoiceDetails.vue";
|
||||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: Object
|
data: Object
|
||||||
@@ -12,7 +13,6 @@
|
|||||||
const invoice = ref(null)
|
const invoice = ref(null)
|
||||||
const show_invoice = ref(false)
|
const show_invoice = ref(false)
|
||||||
const localData = ref(props.data)
|
const localData = ref(props.data)
|
||||||
|
|
||||||
async function openInvoiceDetails(invoiceId) {
|
async function openInvoiceDetails(invoiceId) {
|
||||||
const url = '/api/v1/invoice/details/' + invoiceId
|
const url = '/api/v1/invoice/details/' + invoiceId
|
||||||
|
|
||||||
@@ -40,6 +40,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function exportPayouts() {
|
||||||
|
toast.info('Der Export wird nun gestartet. Bitte verlasse diese Seite nicht, bis der Export abgeschlossen ist.')
|
||||||
|
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||||
|
const data = await response.json();
|
||||||
|
const exportUrl = '/api/v1/cost-unit/' + props.data.costUnit.id + '/export-payouts';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data.tenant.download_exports) {
|
||||||
|
const response = await fetch(exportUrl, {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = "Abrechnungen-Sippenstunden.zip";
|
||||||
|
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
const response = await request(exportUrl, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(response.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
reload()
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -56,7 +100,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td style="width: 150px;">
|
<td style="width: 150px;">
|
||||||
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
||||||
<Icon v-if="invoice.alreadyPaid" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
<Icon v-if="invoice.externalPayment" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{invoice.contactName}}<br />
|
{{invoice.contactName}}<br />
|
||||||
@@ -68,6 +112,13 @@
|
|||||||
<input type="button" value="Abrechnung Anzeigen" @click="openInvoiceDetails(invoice.id)" />
|
<input type="button" value="Abrechnung Anzeigen" @click="openInvoiceDetails(invoice.id)" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr v-if="props.data.endpoint === 'approved'">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<td>
|
||||||
|
<a style="font-size: 10pt;" class="link" @click="exportPayouts()">Genehmigte Abrechnungen exportieren</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p v-else>Es sind keine Abrechnungen in dieser Kategorie vorhanden.</p>
|
<p v-else>Es sind keine Abrechnungen in dieser Kategorie vorhanden.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -56,132 +56,337 @@ const submit = async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppLayout title='Persönliche Daten'>
|
<AppLayout title='Persönliche Daten'>
|
||||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
<shadowed-box class="personal-data-box">
|
||||||
|
|
||||||
<div class="max-w-2xl mx-auto p-6">
|
|
||||||
<form @submit.prevent="submit">
|
<form @submit.prevent="submit">
|
||||||
<table class="form-table" style="width: 90%; margin: 10px;">
|
<!-- Sektion: Stammdaten -->
|
||||||
|
<fieldset class="pd-fieldset">
|
||||||
|
<legend>Stammdaten</legend>
|
||||||
|
|
||||||
<!-- Nicht veränderbare Felder -->
|
<div class="pd-grid">
|
||||||
<tr>
|
<div class="pd-field pd-field--readonly">
|
||||||
<td style="width: 200px; padding: 5px;">Vorname:</td>
|
<label>Vorname</label>
|
||||||
<td><span class="text-gray-700">{{ personalData.firstname }}</span></td>
|
<div class="pd-readonly">{{ personalData.firstname }}</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
|
||||||
<td style="width: 200px; padding: 5px;">Nachname:</td>
|
|
||||||
<td><span class="text-gray-700">{{ personalData.lastname }}</span></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
<div class="pd-field pd-field--readonly">
|
||||||
|
<label>Nachname</label>
|
||||||
|
<div class="pd-readonly">{{ personalData.lastname }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Veränderbare Felder -->
|
<div class="pd-field">
|
||||||
<tr>
|
<label for="nickname">Pfadiname</label>
|
||||||
<td>Pfadiname:</td>
|
<input id="nickname" type="text" v-model="form.nickname" />
|
||||||
<td><input type="text" v-model="form.nickname" /></td>
|
</div>
|
||||||
</tr>
|
|
||||||
<tr>
|
<div class="pd-field">
|
||||||
<td>E-Mail:</td>
|
<label for="birthday">Geburtsdatum</label>
|
||||||
<td><input type="email" v-model="form.email" /></td>
|
<input id="birthday" type="date" v-model="form.birthday" />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>Telefon:</td>
|
</fieldset>
|
||||||
<td><input type="text" v-model="form.phone" /></td>
|
|
||||||
</tr>
|
<!-- Sektion: Kontakt -->
|
||||||
<tr>
|
<fieldset class="pd-fieldset">
|
||||||
<td>Straße / Hausnummer:</td>
|
<legend>Kontakt</legend>
|
||||||
<td><input type="text" v-model="form.address1" /></td>
|
|
||||||
</tr>
|
<div class="pd-grid">
|
||||||
<tr>
|
<div class="pd-field">
|
||||||
<td>Adresszusatz:</td>
|
<label for="email">E-Mail</label>
|
||||||
<td><input type="text" v-model="form.address2" /></td>
|
<input id="email" type="email" v-model="form.email" />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
|
||||||
<td>PLZ:</td>
|
<div class="pd-field">
|
||||||
<td><input type="text" v-model="form.postcode" /></td>
|
<label for="phone">Telefon</label>
|
||||||
</tr>
|
<input id="phone" type="text" v-model="form.phone" />
|
||||||
<tr>
|
</div>
|
||||||
<td>Ort:</td>
|
|
||||||
<td><input type="text" v-model="form.city" /></td>
|
<div class="pd-field pd-field--full">
|
||||||
</tr>
|
<label for="address1">Straße / Hausnummer</label>
|
||||||
<tr>
|
<input id="address1" type="text" v-model="form.address1" />
|
||||||
<td style="width: 200px; padding: 5px;">Geburtsdatum:</td>
|
</div>
|
||||||
<td><input type="date" v-model="form.birthday" /></td>
|
|
||||||
</tr>
|
<div class="pd-field pd-field--full">
|
||||||
<tr>
|
<label for="address2">Adresszusatz</label>
|
||||||
<td>Medikamente:</td>
|
<input id="address2" type="text" v-model="form.address2" />
|
||||||
<td><input type="text" v-model="form.medications" /></td>
|
</div>
|
||||||
</tr>
|
|
||||||
<tr>
|
<div class="pd-field pd-field--narrow">
|
||||||
<td>Allergien:</td>
|
<label for="postcode">PLZ</label>
|
||||||
<td><input type="text" v-model="form.allergies" /></td>
|
<input id="postcode" type="text" v-model="form.postcode" maxlength="5" />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
|
||||||
<td>Unverträglichkeiten:</td>
|
<div class="pd-field pd-field--wide">
|
||||||
<td><input type="text" v-model="form.intolerances" /></td>
|
<label for="city">Ort</label>
|
||||||
</tr>
|
<input id="city" type="text" v-model="form.city" />
|
||||||
<tr>
|
</div>
|
||||||
<td>Letzte Tetanus-Impfung:</td>
|
</div>
|
||||||
<td><input type="date" v-model="form.tetanusVaccination" /></td>
|
</fieldset>
|
||||||
</tr>
|
|
||||||
<tr>
|
<!-- Sektion: Gesundheit -->
|
||||||
<td>Ernährungsgewohnheiten:</td>
|
<fieldset class="pd-fieldset">
|
||||||
<td>
|
<legend>Gesundheit</legend>
|
||||||
<select v-model="form.eatingHabits">
|
|
||||||
|
<div class="pd-grid">
|
||||||
|
<div class="pd-field pd-field--full">
|
||||||
|
<label for="medications">Medikamente</label>
|
||||||
|
<input id="medications" type="text" v-model="form.medications" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pd-field pd-field--full">
|
||||||
|
<label for="allergies">Allergien</label>
|
||||||
|
<input id="allergies" type="text" v-model="form.allergies" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pd-field pd-field--full">
|
||||||
|
<label for="intolerances">Unverträglichkeiten</label>
|
||||||
|
<input id="intolerances" type="text" v-model="form.intolerances" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pd-field">
|
||||||
|
<label for="tetanus">Letzte Tetanus-Impfung</label>
|
||||||
|
<input id="tetanus" type="date" v-model="form.tetanusVaccination" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pd-field">
|
||||||
|
<label for="eating">Ernährungsgewohnheiten</label>
|
||||||
|
<select id="eating" v-model="form.eatingHabits">
|
||||||
<option value="EATING_HABIT_VEGAN">Vegan</option>
|
<option value="EATING_HABIT_VEGAN">Vegan</option>
|
||||||
<option value="EATING_HABIT_VEGETARIAN">Vegetarisch</option>
|
<option value="EATING_HABIT_VEGETARIAN">Vegetarisch</option>
|
||||||
<option value="EATING_HABIT_OMNIVOR">Omnivor</option>
|
<option value="EATING_HABIT_OMNIVOR">Omnivor</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</fieldset>
|
||||||
<td>Badeerlaubnis:</td>
|
|
||||||
<td>
|
<!-- Sektion: Erlaubnisse -->
|
||||||
<select v-model="form.swimmingPermission">
|
<fieldset class="pd-fieldset">
|
||||||
|
<legend>Erlaubnisse</legend>
|
||||||
|
|
||||||
|
<div class="pd-grid">
|
||||||
|
<div class="pd-field pd-field--full">
|
||||||
|
<label for="swimming">Badeerlaubnis</label>
|
||||||
|
<select id="swimming" v-model="form.swimmingPermission">
|
||||||
<option value="SWIMMING_PERMISSION_ALLOWED">Erteilt, kann schwimmen</option>
|
<option value="SWIMMING_PERMISSION_ALLOWED">Erteilt, kann schwimmen</option>
|
||||||
<option value="SWIMMING_PERMISSION_LIMITED">Erteilt, kann nicht schwimmen</option>
|
<option value="SWIMMING_PERMISSION_LIMITED">Erteilt, kann nicht schwimmen</option>
|
||||||
<option value="SWIMMING_PERMISSION_DENIED">Nicht erteilt</option>
|
<option value="SWIMMING_PERMISSION_DENIED">Nicht erteilt</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
<tr>
|
<div class="pd-field pd-field--full">
|
||||||
<td>Erste-Hilfe-Erlaubnis:</td>
|
<label for="firstaid">Erste-Hilfe-Erlaubnis</label>
|
||||||
<td>
|
<select id="firstaid" v-model="form.firstAidPermission">
|
||||||
<select v-model="form.firstAidPermission">
|
|
||||||
<option value="FIRST_AID_PERMISSION_ALLOWED">Erweiterte Erste Hilfe erlaubt</option>
|
<option value="FIRST_AID_PERMISSION_ALLOWED">Erweiterte Erste Hilfe erlaubt</option>
|
||||||
<option value="FIRST_AID_PERMISSION_DENIED">Erweiterte Erste Hilfe verweigert</option>
|
<option value="FIRST_AID_PERMISSION_DENIED">Erweiterte Erste Hilfe verweigert</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</fieldset>
|
||||||
<td>Kontoinhaber*in:</td>
|
|
||||||
<td><input type="text" v-model="form.bankAccountOwner" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>IBAN:</td>
|
|
||||||
<td><IbanInput v-model="form.bankAccountIban" /></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
<!-- Sektion: Bankverbindung -->
|
||||||
<td colspan="2" class="btn-row" style="padding-top: 20px;">
|
<fieldset class="pd-fieldset">
|
||||||
<button type="submit" class="button" :disabled="saving">
|
<legend>Bankverbindung</legend>
|
||||||
|
|
||||||
|
<div class="pd-grid">
|
||||||
|
<div class="pd-field pd-field--full">
|
||||||
|
<label for="owner">Kontoinhaber*in</label>
|
||||||
|
<input id="owner" type="text" v-model="form.bankAccountOwner" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pd-field pd-field--full">
|
||||||
|
<label for="iban">IBAN</label>
|
||||||
|
<IbanInput id="iban" v-model="form.bankAccountIban" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="pd-actions">
|
||||||
|
<button type="submit" class="button pd-submit" :disabled="saving">
|
||||||
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</shadowed-box>
|
</shadowed-box>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
textarea {
|
.personal-data-box {
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-fieldset {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 16px 20px 20px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0 0 8px #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-fieldset legend {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #1d4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid: 2-spaltig auf Desktop */
|
||||||
|
.pd-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 14px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field--full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field--narrow {
|
||||||
|
grid-column: span 1;
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field--wide {
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field input,
|
||||||
|
.pd-field select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 6px 10px;
|
padding: 8px 10px;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
resize: vertical;
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field input:focus,
|
||||||
|
.pd-field select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1d4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-readonly {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px dashed #e5e7eb;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-submit {
|
||||||
|
padding: 10px 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #809dd5;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-submit:hover:not(:disabled) {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-submit:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet (640–1023px) ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.personal-data-box {
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px auto;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-fieldset {
|
||||||
|
padding: 12px 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-grid {
|
||||||
|
gap: 12px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone (< 640px) ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.personal-data-box {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-fieldset {
|
||||||
|
padding: 10px 12px 14px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-fieldset legend {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 3px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid: 1-spaltig auf Smartphone */
|
||||||
|
.pd-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auch "narrow" und "wide" werden volle Breite */
|
||||||
|
.pd-field--narrow,
|
||||||
|
.pd-field--wide,
|
||||||
|
.pd-field--full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field label {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-field input,
|
||||||
|
.pd-field select {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 1rem; /* iOS Zoom-Prevention bei >=16px */
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-actions {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-submit {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\ArchiveEvent;
|
||||||
|
|
||||||
|
class ArchiveEventCommand {
|
||||||
|
public ArchiveEventRequest $request;
|
||||||
|
|
||||||
|
public function __construct(ArchiveEventRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): ArchiveEventResponse {
|
||||||
|
$response = new ArchiveEventResponse();
|
||||||
|
|
||||||
|
$this->request->event->archived = true;
|
||||||
|
$this->request->event->save();
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\ArchiveEvent;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
|
||||||
|
class ArchiveEventRequest {
|
||||||
|
public Event $event;
|
||||||
|
|
||||||
|
public function __construct(Event $event) {
|
||||||
|
$this->event = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\ArchiveEvent;
|
||||||
|
|
||||||
|
class ArchiveEventResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ class SignUpRequest {
|
|||||||
public Tenant $localGroup,
|
public Tenant $localGroup,
|
||||||
public DateTime $birthday,
|
public DateTime $birthday,
|
||||||
public string $address_1,
|
public string $address_1,
|
||||||
public string $address_2,
|
public ?string $address_2,
|
||||||
public string $postcode,
|
public string $postcode,
|
||||||
public string $city,
|
public string $city,
|
||||||
public string $email_1,
|
public string $email_1,
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UnarchiveEvent;
|
||||||
|
|
||||||
|
class UnarchiveEventCommand {
|
||||||
|
public UnarchiveEventRequest $request;
|
||||||
|
|
||||||
|
public function __construct(UnarchiveEventRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UnarchiveEventResponse {
|
||||||
|
$response = new UnarchiveEventResponse();
|
||||||
|
|
||||||
|
$this->request->event->archived = false;
|
||||||
|
$this->request->event->save();
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UnarchiveEvent;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
|
||||||
|
class UnarchiveEventRequest {
|
||||||
|
public Event $event;
|
||||||
|
|
||||||
|
public function __construct(Event $event) {
|
||||||
|
$this->event = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UnarchiveEvent;
|
||||||
|
|
||||||
|
class UnarchiveEventResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ class UpdateParticipantRequest {
|
|||||||
public string $birthday,
|
public string $birthday,
|
||||||
public string $email_1,
|
public string $email_1,
|
||||||
public string $phone_1,
|
public string $phone_1,
|
||||||
public string $contact_person,
|
public ?string $contact_person,
|
||||||
public ?string $email_2,
|
public ?string $email_2,
|
||||||
public ?string $phone_2,
|
public ?string $phone_2,
|
||||||
public string $arrival,
|
public string $arrival,
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class ArchivedEventsController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response {
|
||||||
|
$events = [];
|
||||||
|
foreach ($this->events->getEventsByCriteria(['archived' => true]) as $event) {
|
||||||
|
$events[] = [
|
||||||
|
'id' => $event->id,
|
||||||
|
'name' => $event->name,
|
||||||
|
'location' => $event->location,
|
||||||
|
'postalCode' => $event->postal_code,
|
||||||
|
'eventBegin' => $event->start_date->format('d.m.Y'),
|
||||||
|
'eventEnd' => $event->end_date->format('d.m.Y'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new InertiaProvider('Event/ArchivedEvents', ['events' => $events]))->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,8 @@ class CreateController extends CommonController {
|
|||||||
$payPerDay = $request->input('eventPayPerDay');
|
$payPerDay = $request->input('eventPayPerDay');
|
||||||
$payDirect = $request->input('eventPayDirectly');
|
$payDirect = $request->input('eventPayDirectly');
|
||||||
|
|
||||||
$billingDeadline = $eventEnd->modify('+1 month');
|
$billingDeadline = clone $eventEnd;
|
||||||
|
$billingDeadline->modify('+6 weeks');
|
||||||
|
|
||||||
$createRequest = new CreateEventRequest(
|
$createRequest = new CreateEventRequest(
|
||||||
$request->input('eventName'),
|
$request->input('eventName'),
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Event\Actions\ArchiveEvent\ArchiveEventCommand;
|
||||||
|
use App\Domains\Event\Actions\ArchiveEvent\ArchiveEventRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EventArchiveController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $eventId, Request $request): JsonResponse {
|
||||||
|
$event = $this->events->getByIdentifier($eventId);
|
||||||
|
|
||||||
|
$archiveEventRequest = new ArchiveEventRequest($event);
|
||||||
|
$archiveEventCommand = new ArchiveEventCommand($archiveEventRequest);
|
||||||
|
$response = $archiveEventCommand->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->success ? 'Das Event wurde erfolgreich archiviert.' : 'Beim Archivieren des Events ist ein Fehler aufgetreten.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Event\Actions\UnarchiveEvent\UnarchiveEventCommand;
|
||||||
|
use App\Domains\Event\Actions\UnarchiveEvent\UnarchiveEventRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EventUnarchiveController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $eventId, Request $request): JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
|
||||||
|
$unarchiveRequest = new UnarchiveEventRequest($event);
|
||||||
|
$unarchiveCommand = new UnarchiveEventCommand($unarchiveRequest);
|
||||||
|
$response = $unarchiveCommand->execute();
|
||||||
|
|
||||||
|
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,5 @@ class ParticipantPaymentController extends CommonController
|
|||||||
'class' => $amountLeft->getAmount() != 0 ? 'not-paid' : 'paid',
|
'class' => $amountLeft->getAmount() != 0 ? 'not-paid' : 'paid',
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
dd($participant);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ class SignupController extends CommonController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$signupRequest = new SignUpRequest(
|
$signupRequest = new SignUpRequest(
|
||||||
$event,$registrationData['userId'],
|
$event,
|
||||||
|
$registrationData['userId'] ?? null,
|
||||||
$registrationData['vorname'],
|
$registrationData['vorname'],
|
||||||
$registrationData['nachname'],
|
$registrationData['nachname'],
|
||||||
$registrationData['pfadiname'],
|
$registrationData['pfadiname'],
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use App\Domains\Event\Controllers\CreateController;
|
use App\Domains\Event\Controllers\CreateController;
|
||||||
use App\Domains\Event\Controllers\DetailsController;
|
use App\Domains\Event\Controllers\DetailsController;
|
||||||
|
use App\Domains\Event\Controllers\EventArchiveController;
|
||||||
|
use App\Domains\Event\Controllers\EventUnarchiveController;
|
||||||
use App\Domains\Event\Controllers\MailCompose\ByGroupController;
|
use App\Domains\Event\Controllers\MailCompose\ByGroupController;
|
||||||
use App\Domains\Event\Controllers\MailCompose\SendController;
|
use App\Domains\Event\Controllers\MailCompose\SendController;
|
||||||
use App\Domains\Event\Controllers\ParticipantController;
|
use App\Domains\Event\Controllers\ParticipantController;
|
||||||
@@ -26,6 +28,7 @@ Route::prefix('api/v1')
|
|||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::post('/create', [CreateController::class, 'doCreate']);
|
Route::post('/create', [CreateController::class, 'doCreate']);
|
||||||
|
|
||||||
|
|
||||||
Route::prefix('{eventIdentifier}/mailing')->group(function () {
|
Route::prefix('{eventIdentifier}/mailing')->group(function () {
|
||||||
Route::post('/compose/to-group/{groupType}', ByGroupController::class);
|
Route::post('/compose/to-group/{groupType}', ByGroupController::class);
|
||||||
Route::post('/send', SendController::class);
|
Route::post('/send', SendController::class);
|
||||||
@@ -37,8 +40,8 @@ Route::prefix('api/v1')
|
|||||||
Route::get('/summary', [DetailsController::class, 'summary']);
|
Route::get('/summary', [DetailsController::class, 'summary']);
|
||||||
|
|
||||||
Route::get('/participants/{listType}', [DetailsController::class, 'listParticipants']);
|
Route::get('/participants/{listType}', [DetailsController::class, 'listParticipants']);
|
||||||
|
Route::post('/unarchive', EventUnarchiveController::class);
|
||||||
|
Route::get('/archive', EventArchiveController::class);
|
||||||
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']);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Event\Controllers\ArchivedEventsController;
|
||||||
use App\Domains\Event\Controllers\AvailableEventsController;
|
use App\Domains\Event\Controllers\AvailableEventsController;
|
||||||
use App\Domains\Event\Controllers\CreateController;
|
use App\Domains\Event\Controllers\CreateController;
|
||||||
use App\Domains\Event\Controllers\DetailsController;
|
use App\Domains\Event\Controllers\DetailsController;
|
||||||
@@ -16,10 +17,14 @@ Route::middleware(IdentifyTenant::class)->group(function () {
|
|||||||
Route::get('/{eventId}/signup', SignupController::class);
|
Route::get('/{eventId}/signup', SignupController::class);
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::get('/new', CreateController::class);
|
|
||||||
Route::get('/details/{eventId}', DetailsController::class);
|
Route::get('/details/{eventId}', DetailsController::class);
|
||||||
Route::get('/details/{eventId}/pdf/{listType}', [DetailsController::class, 'downloadPdfList']);
|
Route::get('/details/{eventId}/pdf/{listType}', [DetailsController::class, 'downloadPdfList']);
|
||||||
Route::get('/details/{eventId}/csv/{listType}', [DetailsController::class, 'downloadCsvList']);
|
Route::get('/details/{eventId}/csv/{listType}', [DetailsController::class, 'downloadCsvList']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::get('/create-event', CreateController::class);
|
||||||
|
Route::get('/archived-events', ArchivedEventsController::class);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import { toast } from 'vue3-toastify';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
events: Array,
|
||||||
|
});
|
||||||
|
|
||||||
|
const events = ref([...props.events]);
|
||||||
|
|
||||||
|
async function unarchiveEvent(eventId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`/api/v1/event/details/${eventId}/unarchive`);
|
||||||
|
if (response.data.status === 'success') {
|
||||||
|
events.value = events.value.filter(e => e.id !== eventId);
|
||||||
|
toast.success('Veranstaltung wurde aus dem Archiv geholt.');
|
||||||
|
} else {
|
||||||
|
toast.error('Fehler beim Wiederherstellen der Veranstaltung.');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
toast.error('Ein unerwarteter Fehler ist aufgetreten.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title="Archivierte Veranstaltungen">
|
||||||
|
<div style="width: 95%; margin: 20px auto;">
|
||||||
|
<h1 style="font-size: 1.5rem; font-weight: 700; margin-bottom: 20px;">Archivierte Veranstaltungen</h1>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="events.length === 0"
|
||||||
|
style="text-align: center; color: #6b7280; padding: 40px 0;"
|
||||||
|
>
|
||||||
|
Es sind keine archivierten Veranstaltungen vorhanden.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ShadowedBox
|
||||||
|
v-for="event in events"
|
||||||
|
:key="event.id"
|
||||||
|
style="padding: 20px; margin-bottom: 16px;"
|
||||||
|
>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin: 0 0 4px 0; font-size: 1.1rem; font-weight: 600;">{{ event.name }}</h2>
|
||||||
|
<span style="color: #6b7280; font-size: 0.875rem;">
|
||||||
|
{{ event.postalCode }} {{ event.location }}
|
||||||
|
·
|
||||||
|
{{ event.eventBegin }} – {{ event.eventEnd }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="unarchiveEvent(event.id)"
|
||||||
|
style="
|
||||||
|
padding: 8px 20px;
|
||||||
|
background-color: #f59e0b;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Aus dem Archiv holen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ShadowedBox>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
@@ -8,67 +8,160 @@ const props = defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div style="width: 95%; margin: 20px auto;">
|
<div class="available-events-wrapper">
|
||||||
|
|
||||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
<div v-if="props.events.length === 0" class="available-events-empty">
|
||||||
Aktuell sind keine Veranstaltungen verfügbar.
|
Aktuell sind keine Veranstaltungen verfügbar.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<shadowed-box
|
<shadowed-box
|
||||||
v-for="event in props.events"
|
v-for="event in props.events"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
style="padding: 24px; margin-bottom: 20px;"
|
class="available-event-card"
|
||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px;">
|
<div class="available-event-header">
|
||||||
<div>
|
<div>
|
||||||
<h2 style="margin: 0 0 4px 0; font-size: 1.25rem;">{{ event.name }}</h2>
|
<h2 class="available-event-title">{{ event.name }}</h2>
|
||||||
<span style="color: #6b7280; font-size: 0.9rem;">{{ event.postalCode }} {{ event.location }}</span>
|
<span class="available-event-location">{{ event.postalCode }} {{ event.location }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-if="event.registrationAllowed"
|
v-if="event.registrationAllowed"
|
||||||
style="background: #d1fae5; color: #065f46; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
class="available-event-badge available-event-badge--open"
|
||||||
>
|
>
|
||||||
Anmeldung offen
|
Anmeldung offen
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
style="background: #fee2e2; color: #991b1b; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
class="available-event-badge available-event-badge--closed"
|
||||||
>
|
>
|
||||||
Anmeldung geschlossen
|
Anmeldung geschlossen
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr style="margin: 16px 0; border: none; border-top: 1px solid #e5e7eb;" />
|
<hr class="available-event-divider" />
|
||||||
|
|
||||||
<table style="width: 100%; border-collapse: collapse;">
|
<table class="available-event-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Zeitraum</th>
|
<th>Zeitraum</th>
|
||||||
<td style="padding: 6px 0; color: #111827;">{{ event.eventBegin }} – {{ event.eventEnd }} ({{ event.duration }} Tage)</td>
|
<td>{{ event.eventBegin }} – {{ event.eventEnd }} ({{ event.duration }} Tage)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Veranstaltungsort</th>
|
<th>Veranstaltungsort</th>
|
||||||
<td style="padding: 6px 0; color: #111827;">{{ event.postalCode }} {{ event.location }}</td>
|
<td>{{ event.postalCode }} {{ event.location }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Frühbuchen bis</th>
|
<th>Frühbuchen bis</th>
|
||||||
<td style="padding: 6px 0; color: #111827;">{{ event.earlyBirdEnd.formatted }}</td>
|
<td>{{ event.earlyBirdEnd.formatted }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Anmeldeschluss</th>
|
<th>Anmeldeschluss</th>
|
||||||
<td style="padding: 6px 0; color: #111827;">{{ event.registrationFinalEnd.formatted }}</td>
|
<td>{{ event.registrationFinalEnd.formatted }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="event.email">
|
<tr v-if="event.email">
|
||||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Kontakt</th>
|
<th>Kontakt</th>
|
||||||
<td style="padding: 6px 0;">
|
<td>
|
||||||
<a :href="'mailto:' + event.email" style="color: #2563eb;">{{ event.email }}</a>
|
<a :href="'mailto:' + event.email" class="available-event-mail">{{ event.email }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
<div class="available-event-actions">
|
||||||
<a
|
<a
|
||||||
:href="'/event/' + event.identifier + '/signup'"
|
:href="'/event/' + event.identifier + '/signup'"
|
||||||
style="
|
class="available-event-button"
|
||||||
|
:style="{ opacity: event.registrationAllowed ? '1' : '0.5', pointerEvents: event.registrationAllowed ? 'auto' : 'none' }"
|
||||||
|
>
|
||||||
|
Zur Anmeldung →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</shadowed-box>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.available-events-wrapper {
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-events-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #6b7280;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-card {
|
||||||
|
padding: 24px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-title {
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-location {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-badge {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.available-event-badge--open { background: #d1fae5; color: #065f46; }
|
||||||
|
.available-event-badge--closed { background: #fee2e2; color: #991b1b; }
|
||||||
|
|
||||||
|
.available-event-divider {
|
||||||
|
margin: 16px 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 6px 12px 6px 0;
|
||||||
|
width: 220px;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-table td {
|
||||||
|
padding: 6px 0;
|
||||||
|
color: #111827;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-mail {
|
||||||
|
color: #2563eb;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px 24px;
|
padding: 10px 24px;
|
||||||
background-color: #2563eb;
|
background-color: #2563eb;
|
||||||
@@ -77,24 +170,66 @@ const props = defineProps({
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
opacity: 1;
|
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
"
|
}
|
||||||
:style="{ opacity: event.registrationAllowed ? '1' : '0.5', pointerEvents: event.registrationAllowed ? 'auto' : 'none' }"
|
|
||||||
>
|
|
||||||
Zur Anmeldung →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</shadowed-box>
|
|
||||||
|
|
||||||
</div><div style="width: 95%; margin: 20px auto;">
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.available-events-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
.available-event-table th {
|
||||||
Aktuell sind keine Veranstaltungen verfügbar.
|
width: 160px;
|
||||||
</div>
|
}
|
||||||
</div>
|
}
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.available-event-card {
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-title {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabelle vertikal stapeln: Label über Wert */
|
||||||
|
.available-event-table,
|
||||||
|
.available-event-table tbody,
|
||||||
|
.available-event-table tr,
|
||||||
|
.available-event-table th,
|
||||||
|
.available-event-table td {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-table tr {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-table th {
|
||||||
|
padding: 4px 0 2px 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-table td {
|
||||||
|
padding: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-actions {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-event-button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -56,6 +56,16 @@
|
|||||||
mailCompose.value = true
|
mailCompose.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function archiveEvent() {
|
||||||
|
const response = await fetch("/api/v1/event/details/" + props.data.event.identifier + "/archive" );
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
toast.success(data.message)
|
||||||
|
} else {
|
||||||
|
toast.error(data.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function sendPaymentReminder() {
|
async function sendPaymentReminder() {
|
||||||
toast.info("Die Nachrichten werden gesendet. Bitte verlasse diese Seite nicht.");
|
toast.info("Die Nachrichten werden gesendet. Bitte verlasse diese Seite nicht.");
|
||||||
const response = await fetch("/api/v1/event/" + props.data.event.identifier + "/send-payment-reminder/" );
|
const response = await fetch("/api/v1/event/" + props.data.event.identifier + "/send-payment-reminder/" );
|
||||||
@@ -69,16 +79,41 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="smartphone-actions">
|
||||||
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/first-aid-list'">
|
||||||
|
<input type="button" value="Erste-Hilfe-Liste (PDF)" />
|
||||||
|
</a><br/>
|
||||||
|
|
||||||
|
<a :href="'/event/details/' + props.data.event.identifier + '/csv/participant-list'">
|
||||||
|
<input type="button" value="Teili-Liste (CSV)" />
|
||||||
|
</a><br/>
|
||||||
|
|
||||||
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/kitchen-list'">
|
||||||
|
<input type="button" value="Küchenübersicht (PDF)" />
|
||||||
|
</a><br/>
|
||||||
|
|
||||||
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/amount-list'">
|
||||||
|
<input type="button" value="Beitragsliste (PDF)" />
|
||||||
|
</a><br/>
|
||||||
|
|
||||||
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/drinking-list'">
|
||||||
|
<input type="button" value="Getränkeliste (PDF)" />
|
||||||
|
</a><br/>
|
||||||
|
|
||||||
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/photo-permission-list'">
|
||||||
|
<input type="button" value="Foto-Erlaubnis (PDF)" />
|
||||||
|
</a><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<ParticipationFees v-if="displayData === 'participationFees'" :event="dynamicProps.event" @close="showMain" />
|
<ParticipationFees v-if="displayData === 'participationFees'" :event="dynamicProps.event" @close="showMain" />
|
||||||
<CommonSettings v-else-if="displayData === 'commonSettings'" :event="dynamicProps.event" @close="showMain" />
|
<CommonSettings v-else-if="displayData === 'commonSettings'" :event="dynamicProps.event" @close="showMain" />
|
||||||
<EventManagement v-else-if="displayData === 'eventManagement'" :event="dynamicProps.event" @close="showMain" />
|
<EventManagement v-else-if="displayData === 'eventManagement'" :event="dynamicProps.event" @close="showMain" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="event-flexbox" v-else>
|
<div class="event-flexbox" v-else>
|
||||||
<div class="event-flexbox-row top">
|
<div class="event-flexbox-row top">
|
||||||
<div class="left"><ParticipationSummary v-if="dynamicProps.event" :event="dynamicProps.event" /></div>
|
<div class="actions-left"><ParticipationSummary v-if="dynamicProps.event" :event="dynamicProps.event" /></div>
|
||||||
<div class="right">
|
<div class="actions-right">
|
||||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/first-aid-list'">
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/first-aid-list'">
|
||||||
<input type="button" value="Erste-Hilfe-Liste (PDF)" />
|
<input type="button" value="Erste-Hilfe-Liste (PDF)" />
|
||||||
</a><br/>
|
</a><br/>
|
||||||
@@ -108,6 +143,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -137,7 +174,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Anmelde-URL</th>
|
<th>Anmelde-URL</th>
|
||||||
<td>
|
<td>
|
||||||
{{dynamicProps.event.url}}<br />
|
{{dynamicProps.event.urlShort}}<br />
|
||||||
<img :src="'/print-event-code/' + dynamicProps.event.identifier" alt="Event Code" style="width: 150px; height: 150px; margin-top: 20px;" />
|
<img :src="'/print-event-code/' + dynamicProps.event.identifier" alt="Event Code" style="width: 150px; height: 150px; margin-top: 20px;" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -149,7 +186,9 @@
|
|||||||
<label style="font-size: 9pt;" class="link" @click="showCommonSettings">Allgemeine Einstellungen</label>
|
<label style="font-size: 9pt;" class="link" @click="showCommonSettings">Allgemeine Einstellungen</label>
|
||||||
<label style="font-size: 9pt;" class="link" @click="showEventManagement">Veranstaltungsleitung</label>
|
<label style="font-size: 9pt;" class="link" @click="showEventManagement">Veranstaltungsleitung</label>
|
||||||
<label style="font-size: 9pt;" class="link" @click="showParticipationFees">Teilnahmegebühren</label>
|
<label style="font-size: 9pt;" class="link" @click="showParticipationFees">Teilnahmegebühren</label>
|
||||||
|
<a style="font-size: 9pt;" class="link" :href="'/budget/' + props.data.event.costUnit.id">Budget bearbeiten</a>
|
||||||
<a style="font-size: 9pt;" class="link" :href="'/cost-unit/' + props.data.event.costUnit.id">Ausgabenübersicht</a>
|
<a style="font-size: 9pt;" class="link" :href="'/cost-unit/' + props.data.event.costUnit.id">Ausgabenübersicht</a>
|
||||||
|
<a v-if="!dynamicProps.event.registrationAllowed && !dynamicProps.event.archived" style="color: #ff0000; font-size: 9pt;" class="link" @click="archiveEvent">Archivieren</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -210,13 +249,13 @@
|
|||||||
gap: 10px; /* Abstand zwischen den Spalten */
|
gap: 10px; /* Abstand zwischen den Spalten */
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-flexbox-row.top .left {
|
.event-flexbox-row.top .actions-left {
|
||||||
flex: 0 0 calc(100% - 300px);
|
flex: 0 0 calc(100% - 300px);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-flexbox-row.top .right {
|
.event-flexbox-row.top .actions-right {
|
||||||
flex: 0 0 250px;
|
flex: 0 0 200px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,9 +264,27 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-flexbox-row.top .right input[type="button"] {
|
.event-flexbox-row.top .actions-right input[type="button"] {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.smartphone-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.smartphone-actions {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-flexbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, reactive, watch} from "vue";
|
import {onMounted, reactive, watch} from "vue";
|
||||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||||
|
import DialableTelephoneNumber from "../../../../Views/Components/DialableTelephoneNumber.vue";
|
||||||
|
|
||||||
const staticProps = defineProps({
|
const staticProps = defineProps({
|
||||||
editMode: Boolean,
|
editMode: Boolean,
|
||||||
@@ -86,7 +87,7 @@ watch(
|
|||||||
form.medications = participant?.medications ?? '';
|
form.medications = participant?.medications ?? '';
|
||||||
form.extendedFirstAid = participant?.first_aid_permission ?? '';
|
form.extendedFirstAid = participant?.first_aid_permission ?? '';
|
||||||
form.swimmingPermission = participant?.swimming_permission ?? '';
|
form.swimmingPermission = participant?.swimming_permission ?? '';
|
||||||
form.tetanusVaccination = participant?.tetanus_vaccination ?? '';
|
form.tetanusVaccination = participant?.tetanusVaccinationEdit ?? '';
|
||||||
form.notes = participant?.notes ?? '';
|
form.notes = participant?.notes ?? '';
|
||||||
form.amountPaid = participant?.amountPaid.short ?? '';
|
form.amountPaid = participant?.amountPaid.short ?? '';
|
||||||
form.amountExpected = participant?.amountExpected.short ?? '';
|
form.amountExpected = participant?.amountExpected.short ?? '';
|
||||||
@@ -205,7 +206,7 @@ function saveParticipant() {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Telefon</th>
|
<th>Telefon</th>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="!staticProps.editMode">{{ props.participant.phone_1 }}</span>
|
<DialableTelephoneNumber v-if="!staticProps.editMode" :number="props.participant.phone_1"</DialableTelephoneNumber>
|
||||||
<input v-else v-model="form.phone_1" type="text" />
|
<input v-else v-model="form.phone_1" type="text" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -229,7 +230,7 @@ function saveParticipant() {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Ansprechperson Telefon</th>
|
<th>Ansprechperson Telefon</th>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="!staticProps.editMode">{{ props.participant.phone_2 }}</span>
|
<DialableTelephoneNumber v-if="!staticProps.editMode" :number="props.participant.phone_2"</DialableTelephoneNumber>
|
||||||
<input v-else v-model="form.phone_2" type="text" />
|
<input v-else v-model="form.phone_2" type="text" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js";
|
|||||||
import {format, getDay, getMonth, getYear} from "date-fns";
|
import {format, getDay, getMonth, getYear} from "date-fns";
|
||||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||||
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
||||||
|
import DialableTelephoneNumber from "../../../../Views/Components/DialableTelephoneNumber.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -301,7 +302,7 @@ function mailToGroup(groupKey) {
|
|||||||
<h2>{{ event?.name ?? "Veranstaltung" }}</h2>
|
<h2>{{ event?.name ?? "Veranstaltung" }}</h2>
|
||||||
<div :key="groupKey">
|
<div :key="groupKey">
|
||||||
<div>
|
<div>
|
||||||
<table style="width: 95%; margin: 20px auto; border-collapse: collapse;" v-for="[groupKey, participants] in getGroupEntries">
|
<table class="participants-table" v-for="[groupKey, participants] in getGroupEntries">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="4" style="background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold">
|
<th colspan="4" style="background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold">
|
||||||
@@ -309,10 +310,10 @@ function mailToGroup(groupKey) {
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="background: linear-gradient(to bottom, #fff, #f6f7f7);">
|
<tr style="background: linear-gradient(to bottom, #fff, #f6f7f7);">
|
||||||
<th>Name</th>
|
<th class="pl-name">Name</th>
|
||||||
<th>Beitrag</th>
|
<th class="pl-amount">Beitrag</th>
|
||||||
<th>E-Mail-Adresse</th>
|
<th class="pl-email">E-Mail-Adresse</th>
|
||||||
<th>Telefon</th>
|
<th class="pl-phone">Telefon</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
@@ -323,7 +324,7 @@ function mailToGroup(groupKey) {
|
|||||||
>
|
>
|
||||||
<tr :class="getRowClass(participant)" :id="'participant-' + participant.identifier + '-common'">
|
<tr :class="getRowClass(participant)" :id="'participant-' + participant.identifier + '-common'">
|
||||||
<td :id="'participant-' + participant.identifier +'-name'"
|
<td :id="'participant-' + participant.identifier +'-name'"
|
||||||
style="width: 300px;"
|
class="pl-name"
|
||||||
:class="participant.efz_status === 'checked_invalid' ? 'efz-invalid' :
|
:class="participant.efz_status === 'checked_invalid' ? 'efz-invalid' :
|
||||||
participant.efz_status === 'not_checked' ? 'efz-not-checked' : ''">
|
participant.efz_status === 'not_checked' ? 'efz-not-checked' : ''">
|
||||||
<div :id="'participant-' + participant.identifier +'-fullname'" v-html="participant.fullname" /><br />
|
<div :id="'participant-' + participant.identifier +'-fullname'" v-html="participant.fullname" /><br />
|
||||||
@@ -335,7 +336,7 @@ function mailToGroup(groupKey) {
|
|||||||
<span :id="'participant-' + participant.identifier +'-coc-action'" v-if="participant.efz_status !== 'checked_valid' && participant.efz_status !== 'not_required'" class="link" style="color: #3cb62e; font-size: 11pt;" @click="markCocExisting(participant)">Vorhanden?</span>
|
<span :id="'participant-' + participant.identifier +'-coc-action'" v-if="participant.efz_status !== 'checked_valid' && participant.efz_status !== 'not_required'" class="link" style="color: #3cb62e; font-size: 11pt;" @click="markCocExisting(participant)">Vorhanden?</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !participant.unregistered ? 'not-paid' : ''" style="width: 275px; '">
|
<td class="pl-amount" :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !participant.unregistered ? 'not-paid' : ''">
|
||||||
Gezahlt: <label :id="'participant-' + participant.identifier + '-paid'">{{ participant?.amountPaid.readable }}</label> /<br />
|
Gezahlt: <label :id="'participant-' + participant.identifier + '-paid'">{{ participant?.amountPaid.readable }}</label> /<br />
|
||||||
Gesamt: <label :id="'participant-' + participant.identifier + '-expected'">{{ participant?.amountExpected.readable }}</label>
|
Gesamt: <label :id="'participant-' + participant.identifier + '-expected'">{{ participant?.amountExpected.readable }}</label>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
@@ -345,21 +346,21 @@ function mailToGroup(groupKey) {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td class="pl-email">
|
||||||
<label :id="'participant-' + participant.identifier +'-email_1'" class="block-label">{{ participant?.email_1 ?? "-" }}</label>
|
<label :id="'participant-' + participant.identifier +'-email_1'" class="block-label">{{ participant?.email_1 ?? "-" }}</label>
|
||||||
<label :id="'participant-' + participant.identifier +'-email_2'" class="block-label">{{ participant.email_2 }}</label>
|
<label :id="'participant-' + participant.identifier +'-email_2'" class="block-label">{{ participant.email_2 }}</label>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td class="pl-phone">
|
||||||
<label :id="'participant-' + participant.identifier +'-phone_1'" class="block-label">{{ participant?.phone_1 }}</label>
|
<label :id="'participant-' + participant.identifier +'-phone_1'" class="block-label">P: <DialableTelephoneNumber :number="participant?.phone_1" /></label>
|
||||||
<label :id="'participant-' + participant.identifier +'-phone_2'" class="block-label">{{ participant?.phone_2 }}</label>
|
<label :id="'participant-' + participant.identifier +'-phone_2'" class="block-label">K: <DialableTelephoneNumber :number="participant?.phone_2" /></label>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="participant-meta-row" :id="'participant-' + participant.identifier + '-meta'">
|
<tr class="participant-meta-row" :id="'participant-' + participant.identifier + '-meta'">
|
||||||
<td colspan="5" style="height: 15px !important; font-size: 9pt; background-color: #ffffff; border-top-style: none;">
|
<td colspan="5" class="pl-meta">
|
||||||
<label :id="'participant-' + participant.identifier +'-localgroup'">
|
<label :id="'participant-' + participant.identifier +'-localgroup'">
|
||||||
{{ participant?.localgroup ?? "-" }}
|
{{ participant?.localgroup ?? "-" }}
|
||||||
</label> |
|
</label> |
|
||||||
@@ -385,8 +386,8 @@ function mailToGroup(groupKey) {
|
|||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<tr>
|
<tr class="pl-summary-row">
|
||||||
<td colspan="3" style="font-weight: normal;">
|
<td colspan="3" class="pl-age-summary">
|
||||||
0 - 5 Jahre: <strong>{{ getAgeCounts(participants)['0-5'] ?? 0 }}</strong> |
|
0 - 5 Jahre: <strong>{{ getAgeCounts(participants)['0-5'] ?? 0 }}</strong> |
|
||||||
6-11 Jahre: <strong>{{ getAgeCounts(participants)['6-11'] ?? 0 }}</strong> |
|
6-11 Jahre: <strong>{{ getAgeCounts(participants)['6-11'] ?? 0 }}</strong> |
|
||||||
12-15 Jahre: <strong>{{ getAgeCounts(participants)['12-15'] ?? 0 }}</strong> |
|
12-15 Jahre: <strong>{{ getAgeCounts(participants)['12-15'] ?? 0 }}</strong> |
|
||||||
@@ -394,7 +395,7 @@ function mailToGroup(groupKey) {
|
|||||||
18 - 27 Jahre: <strong>{{ getAgeCounts(participants)['18-27'] ?? 0 }}</strong> |
|
18 - 27 Jahre: <strong>{{ getAgeCounts(participants)['18-27'] ?? 0 }}</strong> |
|
||||||
27 Jahre und älter: <strong>{{ getAgeCounts(participants)['27+'] ?? 0 }}</strong>
|
27 Jahre und älter: <strong>{{ getAgeCounts(participants)['27+'] ?? 0 }}</strong>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="pl-summary-action">
|
||||||
<input type="button" class="button" @click="mailToGroup(groupKey)" value="E-Mail an Gruppe senden" />
|
<input type="button" class="button" @click="mailToGroup(groupKey)" value="E-Mail an Gruppe senden" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -458,81 +459,193 @@ function mailToGroup(groupKey) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
table {
|
.participants-table {
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
margin-bottom: 60px !important;
|
margin-bottom: 60px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr td {
|
tr td {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr th {
|
tr th {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr th:after {
|
tr th:after {
|
||||||
content: "";
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/* Spaltenbreiten auf Desktop */
|
||||||
|
.pl-name { width: 300px; }
|
||||||
|
.pl-amount { width: 275px; }
|
||||||
|
.pl-email { width: auto; }
|
||||||
|
.pl-phone { width: auto; }
|
||||||
|
|
||||||
tr:nth-child(even) {
|
.pl-meta {
|
||||||
|
height: 15px !important;
|
||||||
|
font-size: 9pt;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-top-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
background-color: #f9fafb;
|
background-color: #f9fafb;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0 1px;
|
border-width: 0 1px;
|
||||||
border-color: #e5e7eb;
|
border-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr:nth-child(odd) {
|
||||||
|
|
||||||
tr:nth-child(odd) {
|
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 5px 1px 0 1px;
|
border-width: 5px 1px 0 1px;
|
||||||
border-color: #e5e7eb;
|
border-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:first-child {
|
tr:first-child {
|
||||||
border-width: 1px 1px 0 1px;
|
border-width: 1px 1px 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:last-child {
|
tr:last-child {
|
||||||
border-width: 0 1px 1px 1px;
|
border-width: 0 1px 1px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:last-child td {
|
tr:last-child td {
|
||||||
background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold;
|
background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-paid {
|
.not-paid {
|
||||||
color: #ff0000; background-color: #ffe6e6;
|
color: #ff0000; background-color: #ffe6e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.efz-invalid {
|
.efz-invalid {
|
||||||
color: #ff0000; background-color: #ffe6e6;
|
color: #ff0000; background-color: #ffe6e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.efz-not-checked {
|
.efz-not-checked {
|
||||||
color: #8D914BFF; background-color: #F4E99EFF;
|
color: #8D914BFF; background-color: #F4E99EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.participants-table {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-label {
|
.pl-name { width: 40%; }
|
||||||
display: block;
|
.pl-amount { width: 25%; }
|
||||||
|
.pl-email,
|
||||||
|
.pl-phone { width: 17.5%; }
|
||||||
|
|
||||||
|
tr td {
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 10pt;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.participants-table {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auf Smartphone werden Beitrag und E-Mail ausgeblendet,
|
||||||
|
Name + Telefon bleiben sichtbar */
|
||||||
|
.pl-amount,
|
||||||
|
.pl-email {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-name {
|
||||||
|
width: 65% !important;
|
||||||
|
display: table-cell;
|
||||||
|
padding: 10px 8px !important;
|
||||||
|
height: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-phone {
|
||||||
|
width: 35% !important;
|
||||||
|
display: table-cell;
|
||||||
|
padding: 10px 6px !important;
|
||||||
|
height: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-phone .block-label {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header-Zeile entsprechend anpassen */
|
||||||
|
thead tr:nth-child(2) th.pl-name { width: 65% !important; }
|
||||||
|
thead tr:nth-child(2) th.pl-phone { width: 35% !important; }
|
||||||
|
|
||||||
|
/* Meta-Zeile (Adresse, Aktionen) bleibt sichtbar, aber kompakter */
|
||||||
|
.pl-meta {
|
||||||
|
padding: 8px !important;
|
||||||
|
font-size: 0.78rem !important;
|
||||||
|
line-height: 1.6;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Summary-Zeile (Altersverteilung) auf Mobile: stapeln */
|
||||||
|
.pl-summary-row {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-summary-row td {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-age-summary {
|
||||||
|
font-size: 0.78rem !important;
|
||||||
|
line-height: 1.8;
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-summary-action input.button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tr-Border auf Mobile angleichen */
|
||||||
|
tr td {
|
||||||
|
height: auto;
|
||||||
|
padding: 10px 8px;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
event: Object
|
event: Object
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -68,15 +68,13 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2" style="border-width: 1px; border-bottom-style: solid">Gesamt</th>
|
<th colspan="2" style="border-width: 1px; border-top-style: solid">Gesamt</th>
|
||||||
<td style="font-weight: bold; border-width: 1px; border-bottom-style: solid">
|
<td style="font-weight: bold; border-width: 1px; border-top-style: solid">
|
||||||
{{ props.event.income.real.readable }} /
|
{{ props.event.income.real.readable }} /
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td style="font-weight: bold; border-width: 1px; border-bottom-style: solid">
|
<td style="font-weight: bold; border-width: 1px; border-top-style: solid">
|
||||||
{{ props.event.income.expected.readable }}
|
{{ props.event.income.expected.readable }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -98,55 +96,66 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th style="padding-top: 20px; font-size: 12pt !important;" colspan="2">Budget</th>
|
||||||
|
<td v-if="props.event.totalBalance.expected.value >= 0" style="color: #4caf50; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
|
||||||
|
{{ props.event.totalBalance.estimated.readable }}
|
||||||
|
</td>
|
||||||
|
<td v-else style="color: #f44336; font-weight: bold; padding-top: 20px; font-size: 12pt !important;">
|
||||||
|
{{props.event.totalBalance.estimated.readable}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<h3>Ausgaben</h3>
|
<h3>Ausgaben</h3>
|
||||||
<table class="event-payment-table" style="font-size: 10pt;">
|
<table class="event-payment-table" style="font-size: 10pt; width:100%">
|
||||||
<tr v-for="amount in props.event.costUnit.amounts">
|
<tr v-for="amount in props.event.costUnit.amounts">
|
||||||
<th>{{amount.name}}</th>
|
<th>{{amount.name}}</th>
|
||||||
<td>{{amount.string}}</td>
|
<td>{{amount.string}}</td>
|
||||||
|
<td>({{ amount.estimatedString }}) </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 58px">Gesamt</th>
|
<th style="color:#f44336; border-width: 1px; border-top-style: solid; ">Gesamt</th>
|
||||||
<td style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 58px; font-weight: bold">{{props.event.costUnit.overAllAmount.text}}</td>
|
<td style="color:#f44336; border-width: 1px; border-top-style: solid; font-weight: bold; padding-right: 20px;">{{props.event.costUnit.overAllAmount.text}}</td>
|
||||||
|
<td style="color:#f44336; border-width: 1px; border-top-style: solid; font-weight: bold">({{props.event.costUnit.overAllEstimatedAmount.text}}))</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.participant-flexbox {
|
.participant-flexbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 20px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
margin: 20px auto 0;
|
margin: 20px auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-flexbox-row {
|
.participant-flexbox-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px; /* Abstand zwischen den Spalten */
|
flex: 1 1;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-flexbox-row.top .left {
|
.participant-flexbox-row.top .left,
|
||||||
flex: 0 0 50%;
|
.participant-flexbox-row.top .right {
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.participant-flexbox.top .right {
|
padding: 20px;
|
||||||
flex: 0 0 50%;
|
min-width: 0;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-income-table,
|
.participant-income-table,
|
||||||
.event-payment-table {
|
.event-payment-table {
|
||||||
width: 475px;
|
width: 100%;
|
||||||
|
max-width: 475px;
|
||||||
|
table-layout: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-income-table th {
|
.participant-income-table th {
|
||||||
@@ -155,11 +164,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.participant-income-table tr td:first-child {
|
.participant-income-table tr td:first-child {
|
||||||
width: 25px !important;
|
width: 50px;
|
||||||
font-size: 11pt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-payment-table {
|
.participant-income-table tr td:first-child {
|
||||||
width: 100%;
|
width: 25px !important;
|
||||||
|
font-size: 10pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.event-payment-table th {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -50,43 +50,37 @@ const steps = [
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- Fortschrittsleiste (ab Step 2) -->
|
<!-- Fortschrittsleiste (ab Step 2) -->
|
||||||
<div v-if="currentStep > 1" style="margin-bottom: 28px;">
|
<div v-if="currentStep > 1" class="signup-progress">
|
||||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; align-items: center;">
|
<div class="signup-progress-pills">
|
||||||
<template v-for="(s, index) in steps.filter(s => s.step > 1)" :key="s.step">
|
<template v-for="(s, index) in steps.filter(s => s.step > 1)" :key="s.step">
|
||||||
<!-- Trennlinie zwischen Pills -->
|
<div v-if="index > 0" class="signup-progress-separator"></div>
|
||||||
<div v-if="index > 0" style="flex-shrink: 0; width: 16px; height: 2px; background: #e5e7eb; border-radius: 1px;"></div>
|
|
||||||
<div
|
<div
|
||||||
:style="{
|
class="signup-pill"
|
||||||
padding: '5px 14px',
|
:class="{
|
||||||
borderRadius: '999px',
|
'signup-pill--active': currentStep === s.step,
|
||||||
fontSize: '0.78rem',
|
'signup-pill--done': currentStep > s.step,
|
||||||
fontWeight: '600',
|
'signup-pill--upcoming': currentStep < s.step,
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
border: '2px solid',
|
|
||||||
borderColor: currentStep === s.step ? '#2563eb' : currentStep > s.step ? '#bbf7d0' : '#e5e7eb',
|
|
||||||
background: currentStep === s.step ? '#2563eb' : currentStep > s.step ? '#f0fdf4' : '#f9fafb',
|
|
||||||
color: currentStep === s.step ? 'white' : currentStep > s.step ? '#15803d' : '#9ca3af',
|
|
||||||
cursor: currentStep > s.step ? 'pointer' : 'default',
|
|
||||||
}"
|
}"
|
||||||
@click="currentStep > s.step ? goToStep(s.step) : null"
|
@click="currentStep > s.step ? goToStep(s.step) : null"
|
||||||
>
|
>
|
||||||
<span v-if="currentStep > s.step" style="margin-right: 4px;">✓</span>
|
<span v-if="currentStep > s.step" class="signup-pill__check">✓</span>
|
||||||
{{ s.label }}
|
{{ s.label }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<!-- Fortschrittsbalken -->
|
<!-- Fortschrittsbalken -->
|
||||||
<div style="margin-top: 10px; height: 3px; background: #e5e7eb; border-radius: 2px; overflow: hidden;">
|
<div class="signup-progress-bar">
|
||||||
<div
|
<div
|
||||||
:style="{
|
class="signup-progress-bar__fill"
|
||||||
height: '100%',
|
:style="{ width: ((currentStep - 2) / (steps.length - 2) * 100) + '%' }"
|
||||||
background: 'linear-gradient(90deg, #2563eb, #3b82f6)',
|
|
||||||
borderRadius: '2px',
|
|
||||||
width: ((currentStep - 2) / (steps.length - 2) * 100) + '%',
|
|
||||||
transition: 'width 0.3s ease',
|
|
||||||
}"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Step-Anzeige -->
|
||||||
|
<div class="signup-progress-mobile">
|
||||||
|
Schritt {{ currentStep - 1 }} von {{ steps.length - 1 }}:
|
||||||
|
<strong>{{ steps.find(s => s.step === currentStep)?.label }}</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Steps -->
|
<!-- Steps -->
|
||||||
@@ -115,11 +109,87 @@ const steps = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* ─── Progress (Step-Pills) ─── */
|
||||||
|
.signup-progress {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-pills {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-separator {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill {
|
||||||
|
padding: 5px 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 2px solid;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill__check { margin-right: 4px; }
|
||||||
|
|
||||||
|
.signup-pill--active {
|
||||||
|
border-color: #2563eb;
|
||||||
|
background: #2563eb;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill--done {
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #15803d;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-pill--upcoming {
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
background: #f9fafb;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-bar {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 3px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-bar__fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #2563eb, #3b82f6);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-progress-mobile {
|
||||||
|
display: none;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Form-Table ─── */
|
||||||
.form-table { width: 100%; border-collapse: collapse; }
|
.form-table { width: 100%; border-collapse: collapse; }
|
||||||
.form-table td { padding: 8px 12px 8px 0; vertical-align: top; }
|
.form-table td { padding: 8px 12px 8px 0; vertical-align: top; }
|
||||||
.form-table td:first-child { width: 220px; color: #374151; font-weight: 500; }
|
.form-table td:first-child { width: 220px; color: #374151; font-weight: 500; }
|
||||||
.form-table input[type="text"],
|
.form-table input[type="text"],
|
||||||
.form-table input[type="date"],
|
.form-table input[type="date"],
|
||||||
|
.form-table input[type="email"],
|
||||||
|
.form-table input[type="number"],
|
||||||
.form-table select,
|
.form-table select,
|
||||||
.form-table textarea {
|
.form-table textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -129,7 +199,8 @@ const steps = [
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.btn-row { display: flex; gap: 10px; padding-top: 16px; }
|
|
||||||
|
.btn-row { display: flex; gap: 10px; padding-top: 16px; flex-wrap: wrap; }
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
padding: 8px 20px;
|
padding: 8px 20px;
|
||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
@@ -149,4 +220,58 @@ const steps = [
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.form-table td:first-child {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
/* Pills auf Mobile: kompakter, Trennstriche ausblenden */
|
||||||
|
.signup-progress-pills {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.signup-progress-mobile {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form-Table: Label oberhalb des Feldes */
|
||||||
|
.form-table,
|
||||||
|
.form-table tbody,
|
||||||
|
.form-table tr {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td:first-child {
|
||||||
|
width: 100% !important;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td[colspan] {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-row {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary,
|
||||||
|
.btn-secondary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const emit = defineEmits(['next'])
|
|||||||
<h3 style="margin: 0 0 6px 0; color: #111827;">Wer nimmt teil?</h3>
|
<h3 style="margin: 0 0 6px 0; color: #111827;">Wer nimmt teil?</h3>
|
||||||
<p style="margin: 0 0 24px 0; color: #6b7280; font-size: 0.95rem;">Bitte wähle deine Altersgruppe aus.</p>
|
<p style="margin: 0 0 24px 0; color: #6b7280; font-size: 0.95rem;">Bitte wähle deine Altersgruppe aus.</p>
|
||||||
|
|
||||||
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
|
<div class="age-card-row">
|
||||||
|
|
||||||
<!-- Kind / Jugendliche:r -->
|
<!-- Kind / Jugendliche:r -->
|
||||||
<div class="age-card" @click="emit('next', 2)">
|
<div class="age-card" @click="emit('next', 2)">
|
||||||
@@ -40,9 +40,15 @@ const emit = defineEmits(['next'])
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.age-card-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.age-card {
|
.age-card {
|
||||||
flex: 1;
|
flex: 1 1 280px;
|
||||||
min-width: 220px;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -62,13 +68,14 @@ const emit = defineEmits(['next'])
|
|||||||
|
|
||||||
.age-card__badge {
|
.age-card__badge {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 350px;
|
width: 100%;
|
||||||
height: 200px;
|
max-width: 350px;
|
||||||
|
aspect-ratio: 350 / 200;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.age-card__img {
|
.age-card__img {
|
||||||
width: 350px;
|
width: 100%;
|
||||||
height: 200px;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.age-card__badge-fallback {
|
.age-card__badge-fallback {
|
||||||
@@ -100,4 +107,43 @@ const emit = defineEmits(['next'])
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.age-card-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-card {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
padding: 18px 14px;
|
||||||
|
flex-direction: row;
|
||||||
|
text-align: left;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-card__badge {
|
||||||
|
flex: 0 0 90px;
|
||||||
|
width: 90px;
|
||||||
|
max-width: 90px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-card__body {
|
||||||
|
flex: 1;
|
||||||
|
align-items: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-card__title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-card__desc {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {reactive} from "vue";
|
import {reactive} from "vue";
|
||||||
|
import ErrorText from "../../../../../../Views/Components/ErrorText.vue";
|
||||||
|
|
||||||
const props = defineProps({ formData: Object, localGroups: Array })
|
const props = defineProps({ formData: Object, localGroups: Array })
|
||||||
const emit = defineEmits(['next', 'back'])
|
const emit = defineEmits(['next', 'back'])
|
||||||
@@ -64,6 +65,11 @@ const next = () => {
|
|||||||
hasError = true
|
hasError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.formData.telefon_1) {
|
||||||
|
errors.telefon_1 = 'Bitte eine Telefonnummer angeben.'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
|
||||||
if (!props.formData.email_1) {
|
if (!props.formData.email_1) {
|
||||||
errors.email_1 = 'Bitte eine E-Mail-Adresse angeben.'
|
errors.email_1 = 'Bitte eine E-Mail-Adresse angeben.'
|
||||||
hasError = true
|
hasError = true
|
||||||
@@ -80,8 +86,8 @@ const next = () => {
|
|||||||
<div>
|
<div>
|
||||||
<h3>Persönliche Daten</h3>
|
<h3>Persönliche Daten</h3>
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr><td>Vorname:</td><td><input type="text" v-model="props.formData.vorname" /></td></tr>
|
<tr><td>Vorname:</td><td><input type="text" v-model="props.formData.vorname" /><ErrorText :message="errors.vorname"/></td></tr>
|
||||||
<tr><td>Nachname:</td><td><input type="text" v-model="props.formData.nachname" /></td></tr>
|
<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>Pfadiname:</td><td><input type="text" v-model="props.formData.pfadiname" /></td></tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Stamm:</td>
|
<td>Stamm:</td>
|
||||||
@@ -90,9 +96,10 @@ const next = () => {
|
|||||||
<option value="-1">Bitte wählen</option>
|
<option value="-1">Bitte wählen</option>
|
||||||
<option v-for="lg in localGroups" :key="lg.id" :value="lg.id">{{ lg.name }}</option>
|
<option v-for="lg in localGroups" :key="lg.id" :value="lg.id">{{ lg.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
<ErrorText :message="errors.localGroup" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td>Geburtsdatum:</td><td><input type="date" v-model="props.formData.geburtsdatum" /></td></tr>
|
<tr><td>Geburtsdatum:</td><td><input type="date" v-model="props.formData.geburtsdatum" /><ErrorText :message="errors.geburtsdatum" /></td></tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Adresse:</td>
|
<td>Adresse:</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -103,17 +110,22 @@ const next = () => {
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" v-model="props.formData.address2" />
|
<input type="text" v-model="props.formData.address2" />
|
||||||
|
<ErrorText :message="errors.address1" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>PLZ, Ort:</td>
|
<td>PLZ, Ort:</td>
|
||||||
<td>
|
<td>
|
||||||
<input maxlength="5" type="text" v-model="props.formData.plz" style="width: 100px; margin-right: 8px;" />
|
<div class="plz-ort-row">
|
||||||
<input type="text" v-model="props.formData.ort" style="width: calc(100% - 110px);" />
|
<input maxlength="5" type="text" v-model="props.formData.plz" class="plz-input" placeholder="PLZ" />
|
||||||
|
<input type="text" v-model="props.formData.ort" class="ort-input" placeholder="Ort" />
|
||||||
|
</div>
|
||||||
|
<ErrorText :message="errors.plz" />
|
||||||
|
<ErrorText :message="errors.ort" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td>Telefon:</td><td><input type="text" v-model="props.formData.telefon_1" /></td></tr>
|
<tr><td>Telefon:</td><td><input type="text" v-model="props.formData.telefon_1" /><ErrorText :message="errors.telefon_1" /></td></tr>
|
||||||
<tr><td>E-Mail:</td><td><input type="text" v-model="props.formData.email_1" /></td></tr>
|
<tr><td>E-Mail:</td><td><input type="text" v-model="props.formData.email_1" /><ErrorText :message="errors.email_1" /></td></tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="btn-row">
|
<td colspan="2" class="btn-row">
|
||||||
<button type="button" class="btn-primary" @click="next">Weiter →</button>
|
<button type="button" class="btn-primary" @click="next">Weiter →</button>
|
||||||
@@ -122,3 +134,28 @@ const next = () => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.plz-ort-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plz-input {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ort-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.plz-input {
|
||||||
|
flex: 0 0 80px;
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -26,78 +26,78 @@ function close() {
|
|||||||
<AppLayout title="Für Veranstaltung anmelden">
|
<AppLayout title="Für Veranstaltung anmelden">
|
||||||
<FullScreenModal :show="showSignup" @close="close">
|
<FullScreenModal :show="showSignup" @close="close">
|
||||||
|
|
||||||
<shadowed-box style="width: 95%; margin: 30px auto; padding: 0; overflow: hidden; border-radius: 12px;">
|
<shadowed-box class="signup-box">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div v-if="props.event.registrationAllowed" style="background: linear-gradient(135deg, #1e40af, #3b82f6); padding: 28px 32px; color: white;">
|
<div v-if="props.event.registrationAllowed" class="signup-header signup-header--open">
|
||||||
<div style="display: flex; align-items: center; gap: 14px; flex-wrap: wrap;">
|
<div class="signup-header-row">
|
||||||
<h2 style="margin: 0; font-size: 1.5rem; font-weight: 700;">{{ props.event.name }}</h2>
|
<h2 class="signup-event-title">{{ props.event.name }}</h2>
|
||||||
<span style="background: rgba(255,255,255,0.2); color: white; padding: 4px 14px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap; border: 1px solid rgba(255,255,255,0.4);">
|
<span class="signup-badge signup-badge--open">
|
||||||
✓ Anmeldung offen
|
✓ Anmeldung offen
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin: 8px 0 0 0; opacity: 0.85; font-size: 0.95rem;">
|
<p class="signup-event-location">
|
||||||
📍 {{ props.event.postalCode }} {{ props.event.location }}
|
📍 {{ props.event.postalCode }} {{ props.event.location }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else style="background: linear-gradient(135deg, #991b1b, #dc2626); padding: 28px 32px; color: white;">
|
<div v-else class="signup-header signup-header--closed">
|
||||||
<div style="display: flex; align-items: center; gap: 14px; flex-wrap: wrap;">
|
<div class="signup-header-row">
|
||||||
<h2 style="margin: 0; font-size: 1.5rem; font-weight: 700;">{{ props.event.name }}</h2>
|
<h2 class="signup-event-title">{{ props.event.name }}</h2>
|
||||||
<span style="background: rgba(255,0,0,0.2); color: #fecaca; padding: 4px 14px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap; border: 1px solid rgba(255,100,100,0.4);">
|
<span class="signup-badge signup-badge--closed">
|
||||||
✗ Anmeldung geschlossen
|
✗ Anmeldung geschlossen
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin: 8px 0 0 0; opacity: 0.85; font-size: 0.95rem;">
|
<p class="signup-event-location">
|
||||||
📍 {{ props.event.postalCode }} {{ props.event.location }}
|
📍 {{ props.event.postalCode }} {{ props.event.location }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div style="padding: 28px 32px;">
|
<div class="signup-body">
|
||||||
|
|
||||||
<!-- Info-Grid -->
|
<!-- Info-Grid -->
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;">
|
<div class="signup-info-grid">
|
||||||
|
|
||||||
<!-- Zeile 1 links: Zeitraum -->
|
<!-- Zeitraum -->
|
||||||
<div style="background: #f8fafc; border-radius: 8px; padding: 16px;">
|
<div class="info-card">
|
||||||
<div style="font-size: 0.75rem; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;">Veranstaltungszeitraum</div>
|
<div class="info-card__label">Veranstaltungszeitraum</div>
|
||||||
<div style="font-size: 1rem; color: #111827; font-weight: 500;">{{ props.event.eventBegin }} – {{ props.event.eventEnd }}</div>
|
<div class="info-card__value">{{ props.event.eventBegin }} – {{ props.event.eventEnd }}</div>
|
||||||
<div style="font-size: 0.85rem; color: #6b7280;">{{ props.event.duration }} Tage</div>
|
<div class="info-card__sub">{{ props.event.duration }} Tage</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Zeile 1 rechts: Erstattungsrichtlinien -->
|
<!-- Erstattungsrichtlinien -->
|
||||||
<div style="background: #f0f9ff; border-radius: 8px; padding: 16px; border-left: 3px solid #0ea5e9;">
|
<div class="info-card info-card--accent">
|
||||||
<div style="font-size: 0.75rem; color: #0369a1; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px;">Erstattungsrichtlinien</div>
|
<div class="info-card__label info-card__label--accent">Erstattungsrichtlinien</div>
|
||||||
<div style="color: #0c4a6e; font-size: 0.875rem; line-height: 1.8;">
|
<div class="info-card__list">
|
||||||
<div>100 % bis {{ props.event.earlyBirdEnd.formatted }}</div>
|
<div>100 % bis {{ props.event.earlyBirdEnd.formatted }}</div>
|
||||||
<div>{{ props.event.refundAfterEarlyBirdEnd }} % bis {{ props.event.registrationFinalEnd.formatted }}</div>
|
<div>{{ props.event.refundAfterEarlyBirdEnd }} % bis {{ props.event.registrationFinalEnd.formatted }}</div>
|
||||||
<div>Danach nur bei Nachrückplätzen</div>
|
<div>Danach nur bei Nachrückplätzen</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Zeile 2 links: Anmeldeschluss -->
|
<!-- Anmeldeschluss -->
|
||||||
<div style="background: #f8fafc; border-radius: 8px; padding: 16px;">
|
<div class="info-card">
|
||||||
<div style="font-size: 0.75rem; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;">Anmeldeschluss</div>
|
<div class="info-card__label">Anmeldeschluss</div>
|
||||||
<div style="font-size: 1rem; color: #111827; font-weight: 500; margin-bottom: 20px;">{{ props.event.registrationFinalEnd.formatted }}</div>
|
<div class="info-card__value" style="margin-bottom: 16px;">{{ props.event.registrationFinalEnd.formatted }}</div>
|
||||||
|
|
||||||
<div style="font-size: 0.75rem; color: #366a34; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;">Frühbucher bis</div>
|
<div class="info-card__label info-card__label--green">Frühbucher bis</div>
|
||||||
<div style="font-size: 1rem; color: #366a34; font-weight: 500;">{{ props.event.earlyBirdEnd.formatted }}</div>
|
<div class="info-card__value" style="color: #366a34;">{{ props.event.earlyBirdEnd.formatted }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Zeile 2 rechts: Kontakt -->
|
<!-- Kontakt -->
|
||||||
<div style="background: #f8fafc; border-radius: 8px; padding: 16px;">
|
<div class="info-card">
|
||||||
<div style="font-size: 0.75rem; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px;">Kontakt</div>
|
<div class="info-card__label">Kontakt</div>
|
||||||
<p style="margin: 0 0 6px 0; color: #374151; font-size: 0.9rem; line-height: 1.6;">
|
<p class="info-card__text">
|
||||||
Hast du Fragen zur Veranstaltung oder deiner Anmeldung? Kontaktiere uns per E-Mail:
|
Hast du Fragen zur Veranstaltung oder deiner Anmeldung? Kontaktiere uns per E-Mail:
|
||||||
</p>
|
</p>
|
||||||
<a :href="'mailto:' + props.event.email" style="font-size: 0.95rem; color: #2563eb; text-decoration: none; font-weight: 500;">{{ props.event.email }}</a>
|
<a :href="'mailto:' + props.event.email" class="info-card__link">{{ props.event.email }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class="signup-divider" />
|
||||||
|
|
||||||
<hr style="margin: 0 32px; border: none; border-top: 1px solid #e5e7eb;" />
|
<div class="signup-body">
|
||||||
<div style="padding: 28px 32px;">
|
|
||||||
<SignupForm
|
<SignupForm
|
||||||
:event="props.event"
|
:event="props.event"
|
||||||
:participantData="props.participantData ?? {}"
|
:participantData="props.participantData ?? {}"
|
||||||
@@ -115,5 +115,151 @@ function close() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.signup-box {
|
||||||
|
width: 95%;
|
||||||
|
margin: 30px auto;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.signup-header {
|
||||||
|
padding: 28px 32px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.signup-header--open { background: linear-gradient(135deg, #1e40af, #3b82f6); }
|
||||||
|
.signup-header--closed { background: linear-gradient(135deg, #991b1b, #dc2626); }
|
||||||
|
|
||||||
|
.signup-header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.signup-event-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.signup-badge {
|
||||||
|
padding: 4px 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 1px solid rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
.signup-badge--open { background: rgba(255,255,255,0.2); color: white; }
|
||||||
|
.signup-badge--closed { background: rgba(255,0,0,0.2); color: #fecaca; }
|
||||||
|
|
||||||
|
.signup-event-location {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
opacity: 0.85;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
.signup-body { padding: 28px 32px; }
|
||||||
|
.signup-divider { margin: 0 32px; border: none; border-top: 1px solid #e5e7eb; }
|
||||||
|
|
||||||
|
/* Info-Grid */
|
||||||
|
.signup-info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.info-card--accent {
|
||||||
|
background: #f0f9ff;
|
||||||
|
border-left: 3px solid #0ea5e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card__label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.info-card__label--accent { color: #0369a1; margin-bottom: 8px; }
|
||||||
|
.info-card__label--green { color: #366a34; margin-top: 4px; }
|
||||||
|
|
||||||
|
.info-card__value {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #111827;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.info-card__sub {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
.info-card__list {
|
||||||
|
color: #0c4a6e;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
.info-card__text {
|
||||||
|
margin: 0 0 6px 0;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.info-card__link {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet (640–1023px) ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.signup-box {
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
.signup-header,
|
||||||
|
.signup-body { padding: 20px 18px; }
|
||||||
|
.signup-divider { margin: 0 18px; }
|
||||||
|
|
||||||
|
.signup-info-grid {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.signup-event-title { font-size: 1.25rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone (< 640px) ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.signup-box {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.signup-header,
|
||||||
|
.signup-body { padding: 16px 12px; }
|
||||||
|
.signup-divider { margin: 0 12px; }
|
||||||
|
|
||||||
|
.signup-event-title { font-size: 1.1rem; }
|
||||||
|
.signup-event-location { font-size: 0.85rem; }
|
||||||
|
.signup-badge { font-size: 0.7rem; padding: 3px 10px; }
|
||||||
|
|
||||||
|
.signup-info-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card { padding: 12px; }
|
||||||
|
.info-card__value { font-size: 0.95rem; }
|
||||||
|
.info-card__list { font-size: 0.82rem; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class CreateInvoiceCommand {
|
|||||||
'travel_reason' => $this->request->travelReason,
|
'travel_reason' => $this->request->travelReason,
|
||||||
'passengers' => $this->request->passengers,
|
'passengers' => $this->request->passengers,
|
||||||
'transportation' => $this->request->transportations,
|
'transportation' => $this->request->transportations,
|
||||||
|
'payment_purpose' => $this->request->paymentPurpose,
|
||||||
|
'comment' => $this->request->notices,
|
||||||
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->fullPath : null,
|
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->fullPath : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ class CreateInvoiceRequest {
|
|||||||
public bool $isDonation;
|
public bool $isDonation;
|
||||||
public ?int $userId;
|
public ?int $userId;
|
||||||
public ?string $travelReason;
|
public ?string $travelReason;
|
||||||
|
public ?string $paymentPurpose;
|
||||||
|
public ?string $notices;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -43,6 +45,8 @@ class CreateInvoiceRequest {
|
|||||||
?int $passengers = null,
|
?int $passengers = null,
|
||||||
?int $transportations,
|
?int $transportations,
|
||||||
?string $travelReason = null,
|
?string $travelReason = null,
|
||||||
|
?string $paymentPurpose = null,
|
||||||
|
?string $notices = null,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
$this->costUnit = $costUnit;
|
$this->costUnit = $costUnit;
|
||||||
@@ -62,6 +66,8 @@ class CreateInvoiceRequest {
|
|||||||
$this->isDonation = $isDonation;
|
$this->isDonation = $isDonation;
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
$this->travelReason = $travelReason;
|
$this->travelReason = $travelReason;
|
||||||
|
$this->paymentPurpose = $paymentPurpose;
|
||||||
|
$this->notices = $notices;
|
||||||
|
|
||||||
if ($accountIban === 'undefined') {
|
if ($accountIban === 'undefined') {
|
||||||
$this->accountIban = null;
|
$this->accountIban = null;
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ class CreateInvoiceReceiptCommand {
|
|||||||
storage_path('app/private/' .$filename) . '.zip',
|
storage_path('app/private/' .$filename) . '.zip',
|
||||||
ZipArchive::CREATE | ZipArchive::OVERWRITE
|
ZipArchive::CREATE | ZipArchive::OVERWRITE
|
||||||
);
|
);
|
||||||
|
|
||||||
$zip->addFile(storage_path('app/private/' . $tmpFileName ), 'antrag.pdf');
|
$zip->addFile(storage_path('app/private/' . $tmpFileName ), 'antrag.pdf');
|
||||||
$zip->addFile(storage_path('app/private/' . $receipt), 'beleg.pdf');
|
$zip->addFile(storage_path('app/private/' . $receipt), 'beleg.pdf');
|
||||||
|
|
||||||
@@ -114,6 +113,9 @@ HTML;
|
|||||||
<tr><td>Name:</td><td>%3\$s</td></tr>
|
<tr><td>Name:</td><td>%3\$s</td></tr>
|
||||||
<tr><td>E-Mail:</td><td>%4\$s</td></tr>
|
<tr><td>E-Mail:</td><td>%4\$s</td></tr>
|
||||||
<tr><td>Telefon:</td><td>%5\$s</td></tr>
|
<tr><td>Telefon:</td><td>%5\$s</td></tr>
|
||||||
|
<tr><td>Anmerkungen:</td><td>%16\$s</td></tr>
|
||||||
|
|
||||||
|
|
||||||
<tr><td>Name der Kostenstelle:</td><td>%6\$s</td></tr>
|
<tr><td>Name der Kostenstelle:</td><td>%6\$s</td></tr>
|
||||||
<tr><td>Zahlungsgrund:</td><td>%7\$s</td></tr>
|
<tr><td>Zahlungsgrund:</td><td>%7\$s</td></tr>
|
||||||
<tr><td>Wird der Betrag gespendet:</td><td>%8\$s</td></tr>
|
<tr><td>Wird der Betrag gespendet:</td><td>%8\$s</td></tr>
|
||||||
@@ -186,7 +188,8 @@ HTML;
|
|||||||
$invoiceReadable['createdAt'],
|
$invoiceReadable['createdAt'],
|
||||||
$invoiceReadable['approvedAt'],
|
$invoiceReadable['approvedAt'],
|
||||||
$invoiceReadable['approvedBy'],
|
$invoiceReadable['approvedBy'],
|
||||||
$changes
|
$changes,
|
||||||
|
$invoiceReadable['comment'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ use App\Models\Invoice;
|
|||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
class UpdateInvoiceRequest {
|
class UpdateInvoiceRequest {
|
||||||
public string $comment;
|
public ?string $comment;
|
||||||
public InvoiceType $invoiceType;
|
public InvoiceType $invoiceType;
|
||||||
public CostUnit $costUnit;
|
public CostUnit $costUnit;
|
||||||
public Invoice $invoice;
|
public Invoice $invoice;
|
||||||
public Amount $amount;
|
public Amount $amount;
|
||||||
|
|
||||||
public function __construct(Invoice $invoice, string $comment, InvoiceType $invoiceType, CostUnit $costUnit, Amount $amount) {
|
public function __construct(Invoice $invoice, ?string $comment, InvoiceType $invoiceType, CostUnit $costUnit, Amount $amount) {
|
||||||
$this->comment = $comment;
|
$this->comment = $comment;
|
||||||
$this->invoiceType = $invoiceType;
|
$this->invoiceType = $invoiceType;
|
||||||
$this->costUnit = $costUnit;
|
$this->costUnit = $costUnit;
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ class EditController extends CommonController{
|
|||||||
$invoice->travel_direction,
|
$invoice->travel_direction,
|
||||||
$invoice->distance,
|
$invoice->distance,
|
||||||
$invoice->passengers,
|
$invoice->passengers,
|
||||||
$invoice->transportation
|
$invoice->transportation,
|
||||||
|
$invoice->travel_reason,
|
||||||
|
$invoice->payment_purpose,
|
||||||
|
$invoice->comment,
|
||||||
);
|
);
|
||||||
|
|
||||||
$invoiceCreationCommand = new CreateInvoiceCommand($createInvoiceRequest);
|
$invoiceCreationCommand = new CreateInvoiceCommand($createInvoiceRequest);
|
||||||
@@ -86,7 +89,7 @@ class EditController extends CommonController{
|
|||||||
|
|
||||||
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
||||||
$invoice,
|
$invoice,
|
||||||
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
$modifyData['notices'],
|
||||||
$invoiceType,
|
$invoiceType,
|
||||||
$newCostUnit,
|
$newCostUnit,
|
||||||
$newAmount
|
$newAmount
|
||||||
|
|||||||
@@ -34,102 +34,4 @@ class NewInvoiceController extends CommonController {
|
|||||||
]);
|
]);
|
||||||
return $inertiaProvider->render();
|
return $inertiaProvider->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveInvoice(Request $request, int $costUnitId, string $invoiceType) : JsonResponse {
|
|
||||||
$costUnit = $this->costUnits->getById($costUnitId, true);
|
|
||||||
if (null === $costUnit) {
|
|
||||||
return response()->json([
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => 'Beim Speichern ist ein Fehler aufgetreten. Bitte starte den Vorgang erneut.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$uploadedFile = null;
|
|
||||||
if (null !== $request->file('receipt')) {
|
|
||||||
$validation = sprintf('%1$s|%2$s|max:%3$s',
|
|
||||||
'required',
|
|
||||||
'mimes:pdf',
|
|
||||||
env('MAX_INVOICE_FILE_SIZE', 16)*10
|
|
||||||
);
|
|
||||||
|
|
||||||
$request->validate([
|
|
||||||
'receipt' => $validation
|
|
||||||
]);
|
|
||||||
|
|
||||||
$uploadFileProvider = new UploadFileProvider($request->file('receipt'), $costUnit);
|
|
||||||
$uploadedFile = $uploadFileProvider->saveUploadedFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($invoiceType) {
|
|
||||||
case InvoiceType::INVOICE_TYPE_TRAVELLING:
|
|
||||||
|
|
||||||
if ($uploadedFile !== null) {
|
|
||||||
$amount = Amount::fromString($request->input('amount'))->getAmount();
|
|
||||||
$distance = null;
|
|
||||||
} else {
|
|
||||||
$distance = Amount::fromString($request->input('amount'))->getRoundedAmount();
|
|
||||||
$amount = $distance * $costUnit->distance_allowance;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
|
||||||
$costUnit,
|
|
||||||
$request->input('name'),
|
|
||||||
InvoiceType::INVOICE_TYPE_TRAVELLING,
|
|
||||||
$amount,
|
|
||||||
$uploadedFile,
|
|
||||||
'donation' === $request->input('decision') ? true : false,
|
|
||||||
$this->users->getCurrentUserDetails()['userId'],
|
|
||||||
$request->input('email'),
|
|
||||||
$request->input('telephone'),
|
|
||||||
$request->input('accountOwner'),
|
|
||||||
$request->input('accountIban'),
|
|
||||||
null,
|
|
||||||
$request->input('otherText'),
|
|
||||||
$distance,
|
|
||||||
$request->input('havePassengers'),
|
|
||||||
$request->input('materialTransportation'),
|
|
||||||
$request->input('travelReason'),
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
|
||||||
$costUnit,
|
|
||||||
$request->input('name'),
|
|
||||||
$invoiceType,
|
|
||||||
Amount::fromString($request->input('amount'))->getAmount(),
|
|
||||||
$uploadedFile,
|
|
||||||
'donation' === $request->input('decision') ? true : false,
|
|
||||||
$this->users->getCurrentUserDetails()['userId'],
|
|
||||||
$request->input('email'),
|
|
||||||
$request->input('telephone'),
|
|
||||||
$request->input('accountOwner'),
|
|
||||||
$request->input('accountIban'),
|
|
||||||
$request->input('otherText'),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
$request->input('havePassengers'),
|
|
||||||
$request->input('materialTransportation'),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$command = new CreateInvoiceCommand($createInvoiceRequest);
|
|
||||||
$response = $command->execute();
|
|
||||||
if ($response->success) {
|
|
||||||
new FlashMessageProvider(
|
|
||||||
'Die Abrechnung wurde erfolgreich angelegt.' . PHP_EOL . PHP_EOL . 'Sollten wir Rückfragen haben, melden wir uns bei dir',
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'status' => 'success',
|
|
||||||
'message' => 'Alright'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceCommand;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Providers\FlashMessageProvider;
|
||||||
|
use App\Providers\UploadFileProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class SaveInvoiceController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, int $costUnitId, string $invoiceType) : JsonResponse {
|
||||||
|
$costUnit = $this->costUnits->getById($costUnitId, true);
|
||||||
|
$paymentPurpose = $request->input('paymentPurpose') ?? null;
|
||||||
|
$notices = $request->input('notices') ?? null;
|
||||||
|
if (null === $costUnit) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Beim Speichern ist ein Fehler aufgetreten. Bitte starte den Vorgang erneut.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadedFile = null;
|
||||||
|
if (null !== $request->file('receipt')) {
|
||||||
|
$maxFileSize = env('MAX_INVOICE_FILE_SIZE', 16);
|
||||||
|
$validation = sprintf('%1$s|%2$s|max:%3$s',
|
||||||
|
'required',
|
||||||
|
'mimes:pdf',
|
||||||
|
$maxFileSize * 1024
|
||||||
|
);
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'receipt' => $validation
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => sprintf(
|
||||||
|
'Der Beleg konnte nicht hochgeladen werden. Bitte beachte die Maximale Dateigröße von %1$s MB sowie die Tatsache, dass ausschließlich PDF-Dateien akzeptiert werden.',
|
||||||
|
$maxFileSize
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadFileProvider = new UploadFileProvider($request->file('receipt'), $costUnit);
|
||||||
|
$uploadedFile = $uploadFileProvider->saveUploadedFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($invoiceType) {
|
||||||
|
case InvoiceType::INVOICE_TYPE_TRAVELLING:
|
||||||
|
|
||||||
|
if ($uploadedFile !== null) {
|
||||||
|
$amount = Amount::fromString($request->input('amount'))->getAmount();
|
||||||
|
$distance = null;
|
||||||
|
} else {
|
||||||
|
$distance = Amount::fromString($request->input('amount'))->getRoundedAmount();
|
||||||
|
$amount = $distance * $costUnit->distance_allowance;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||||
|
$costUnit,
|
||||||
|
$request->input('name'),
|
||||||
|
InvoiceType::INVOICE_TYPE_TRAVELLING,
|
||||||
|
$amount,
|
||||||
|
$uploadedFile,
|
||||||
|
'donation' === $request->input('decision') ? true : false,
|
||||||
|
$this->users->getCurrentUserDetails()['userId'],
|
||||||
|
$request->input('email'),
|
||||||
|
$request->input('telephone'),
|
||||||
|
$request->input('accountOwner'),
|
||||||
|
$request->input('accountIban'),
|
||||||
|
null,
|
||||||
|
$request->input('otherText'),
|
||||||
|
$distance,
|
||||||
|
$request->input('havePassengers'),
|
||||||
|
$request->input('materialTransportation'),
|
||||||
|
$request->input('travelReason'),
|
||||||
|
null,
|
||||||
|
$notices
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||||
|
$costUnit,
|
||||||
|
$request->input('name'),
|
||||||
|
$invoiceType,
|
||||||
|
Amount::fromString($request->input('amount'))->getAmount(),
|
||||||
|
$uploadedFile,
|
||||||
|
'donation' === $request->input('decision') ? true : false,
|
||||||
|
$this->users->getCurrentUserDetails()['userId'],
|
||||||
|
$request->input('email'),
|
||||||
|
$request->input('telephone'),
|
||||||
|
$request->input('accountOwner'),
|
||||||
|
$request->input('accountIban'),
|
||||||
|
$request->input('otherText'),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$request->input('havePassengers'),
|
||||||
|
$request->input('materialTransportation'),
|
||||||
|
null,
|
||||||
|
$paymentPurpose,
|
||||||
|
$notices
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = new CreateInvoiceCommand($createInvoiceRequest);
|
||||||
|
$response = $command->execute();
|
||||||
|
if ($response->success) {
|
||||||
|
new FlashMessageProvider(
|
||||||
|
'Die Abrechnung wurde erfolgreich angelegt.' . PHP_EOL . PHP_EOL . 'Sollten wir Rückfragen haben, melden wir uns bei dir',
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Alright'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,14 @@ use App\Domains\Invoice\Controllers\ChangeStateController;
|
|||||||
use App\Domains\Invoice\Controllers\EditController;
|
use App\Domains\Invoice\Controllers\EditController;
|
||||||
use App\Domains\Invoice\Controllers\ListMyInvoicesController;
|
use App\Domains\Invoice\Controllers\ListMyInvoicesController;
|
||||||
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
||||||
|
use App\Domains\Invoice\Controllers\SaveInvoiceController;
|
||||||
use App\Domains\Invoice\Controllers\ShowInvoiceController;
|
use App\Domains\Invoice\Controllers\ShowInvoiceController;
|
||||||
use App\Middleware\IdentifyTenant;
|
use App\Middleware\IdentifyTenant;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
Route::prefix('api/v1/invoice')->group(function () {
|
Route::prefix('api/v1/invoice')->group(function () {
|
||||||
Route::post('/new/{costUnitId}/{invoiceType}', [NewInvoiceController::class, 'saveInvoice']);
|
Route::post('/new/{costUnitId}/{invoiceType}', SaveInvoiceController::class);
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::get('/details/{invoiceId}', ShowInvoiceController::class);
|
Route::get('/details/{invoiceId}', ShowInvoiceController::class);
|
||||||
Route::get('/showReceipt/{invoiceId}', [ShowInvoiceController::class, 'showReceipt']);
|
Route::get('/showReceipt/{invoiceId}', [ShowInvoiceController::class, 'showReceipt']);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Modal from "../../../Views/Components/Modal.vue";
|
|||||||
import {onMounted, reactive, ref} from "vue";
|
import {onMounted, reactive, ref} from "vue";
|
||||||
import ExpenseAccounting from "./Partials/newInvoice/expense-accounting.vue";
|
import ExpenseAccounting from "./Partials/newInvoice/expense-accounting.vue";
|
||||||
import TravelExpenseAccounting from "./Partials/newInvoice/travel-expense-accounting.vue";
|
import TravelExpenseAccounting from "./Partials/newInvoice/travel-expense-accounting.vue";
|
||||||
|
import DirectPayment from "./Partials/newInvoice/direct-payment.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentEvents: Object,
|
currentEvents: Object,
|
||||||
@@ -42,6 +43,24 @@ const invoiceType = ref('');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="eventId === 0"
|
||||||
|
class="invoice-type-layer" style="width: calc(100% - 60px); margin: auto"
|
||||||
|
@click="isOpen = true;invoiceType='direct-payment'"
|
||||||
|
>
|
||||||
|
<TextResource textName="NEW_DIRECT_PAYMENT" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DirectPayment v-if="invoiceType === 'direct-payment' && eventId !== 0"
|
||||||
|
:eventId="eventId"
|
||||||
|
:userName="props.userName"
|
||||||
|
:userEmail="props.userEmail"
|
||||||
|
:userTelephone="props.userTelephone"
|
||||||
|
:userAccountIban="props.userAccountIban"
|
||||||
|
:userAccountOwner="props.userAccountOwner"
|
||||||
|
:userId="props.userId"
|
||||||
|
/>
|
||||||
|
|
||||||
<ExpenseAccounting v-if="invoiceType === 'expense-accounting' && eventId !== 0"
|
<ExpenseAccounting v-if="invoiceType === 'expense-accounting' && eventId !== 0"
|
||||||
:eventId="eventId"
|
:eventId="eventId"
|
||||||
:userName="props.userName"
|
:userName="props.userName"
|
||||||
@@ -62,7 +81,7 @@ const invoiceType = ref('');
|
|||||||
:userId="props.userId"
|
:userId="props.userId"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal :show="isOpen" title="Veranstaltung auswählen" @close="isOpen = false">
|
<Modal :show="isOpen" title="Veranstaltung auswählen" @close="isOpen = false" width="450px">
|
||||||
<select v-model="eventId" @change="isOpen=false" style="width: 100%">
|
<select v-model="eventId" @change="isOpen=false" style="width: 100%">
|
||||||
<option value="0" disabled>Bitte auswählen</option>
|
<option value="0" disabled>Bitte auswählen</option>
|
||||||
<optgroup label="Laufende Tätigkeiten">
|
<optgroup label="Laufende Tätigkeiten">
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const formData = reactive({
|
|||||||
type_internal: props.newInvoice.internalType || '',
|
type_internal: props.newInvoice.internalType || '',
|
||||||
cost_unit: props.newInvoice.costUnitId || '',
|
cost_unit: props.newInvoice.costUnitId || '',
|
||||||
amount: props.newInvoice.amountPlain || '',
|
amount: props.newInvoice.amountPlain || '',
|
||||||
reason_of_correction: '',
|
notices: props.newInvoice.comments || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const submitForm = () => {
|
const submitForm = () => {
|
||||||
@@ -71,9 +71,13 @@ onMounted(async () => {
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Grund der Korrektur:</td>
|
<td>Anmerkungen:</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" v-model="formData.reason_of_correction" class="width-half-full" />
|
<textarea
|
||||||
|
id="notices"
|
||||||
|
name="notices" v-model="formData.notices"
|
||||||
|
maxlength="128"
|
||||||
|
style="font-size: 14pt; width: 550px;" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const emit = defineEmits(["accept", "deny", "fix", "reopen"])
|
|||||||
<td>{{props.data.accountIban}}</td>
|
<td>{{props.data.accountIban}}</td>
|
||||||
<td>Buchungsinformationen:</td>
|
<td>Buchungsinformationen:</td>
|
||||||
<td v-if="props.data.donation">Als Spende gebucht</td>
|
<td v-if="props.data.donation">Als Spende gebucht</td>
|
||||||
<td v-else-if="props.data.alreadyPaid">Beleg ohne Auszahlung</td>
|
<td v-else-if="props.data.externalPayment">Rechnungszahlung</td>
|
||||||
<td v-else>Klassische Auszahlung</td>
|
<td v-else>Klassische Auszahlung</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const emit = defineEmits(["accept", "deny", "fix", "reopen"])
|
|||||||
<td>{{props.data.accountIban}}</td>
|
<td>{{props.data.accountIban}}</td>
|
||||||
<td>Buchungsinformationen:</td>
|
<td>Buchungsinformationen:</td>
|
||||||
<td v-if="props.data.donation">Als Spende gebucht</td>
|
<td v-if="props.data.donation">Als Spende gebucht</td>
|
||||||
<td v-else-if="props.data.alreadyPaid">Beleg ohne Auszahlung</td>
|
<td v-else-if="props.data.externalPayment">Rechnungszahlung</td>
|
||||||
<td v-else>Klassische Auszahlung</td>
|
<td v-else>Klassische Auszahlung</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td style="width: 150px;">
|
<td style="width: 150px;">
|
||||||
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
||||||
<Icon v-if="invoice.alreadyPaid" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
<Icon v-if="invoice.externalPayment" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{invoice.contactName}}<br />
|
{{invoice.contactName}}<br />
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
|
import {checkFilesize} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
||||||
|
import RefundData from "./refund-data.vue";
|
||||||
|
import AmountInput from "../../../../../Views/Components/AmountInput.vue";
|
||||||
|
import {useAjax} from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import InfoIcon from '../../../../../Views/Components/InfoIcon.vue'
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import PaymentData from "./payment-data.vue";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const data = defineProps({
|
||||||
|
eventId: Number,
|
||||||
|
userName: String,
|
||||||
|
userEmail: String,
|
||||||
|
userTelephone: String,
|
||||||
|
userAccountIban: String,
|
||||||
|
userAccountOwner: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const { request } = useAjax();
|
||||||
|
const amount = ref(0.00);
|
||||||
|
const invoiceType = ref(null);
|
||||||
|
const otherText = ref('');
|
||||||
|
const receipt = ref(null)
|
||||||
|
const finalStep = ref(false)
|
||||||
|
|
||||||
|
const invoiceTypeCollection = reactive({
|
||||||
|
invoiceTypes: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-invoice-types');
|
||||||
|
const data = await response.json();
|
||||||
|
Object.assign(invoiceTypeCollection, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function handleFileChange(event) {
|
||||||
|
if (checkFilesize('receipt')) {
|
||||||
|
receipt.value = event.target.files[0]
|
||||||
|
finalStep.value = true
|
||||||
|
} else {
|
||||||
|
event.target.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<fieldset>
|
||||||
|
<legend><span style="font-weight: bolder;">Was ist der Anlass der Rechnung</span></legend>
|
||||||
|
|
||||||
|
<p v-for="availableInvoiceType in invoiceTypeCollection.invoiceTypes">
|
||||||
|
<input
|
||||||
|
name="invpice_type"
|
||||||
|
type="radio"
|
||||||
|
:value="availableInvoiceType.slug"
|
||||||
|
:id="'invoice_type_' + availableInvoiceType.slug"
|
||||||
|
v-model="invoiceType"
|
||||||
|
>
|
||||||
|
<label :for="'invoice_type_' + availableInvoiceType.slug">{{ availableInvoiceType.name }}</label>
|
||||||
|
<InfoIcon :text="'INFO_INVOICE_TYPE_' + availableInvoiceType.slug" /><br />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<label for="invoice_type_other">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="width-full"
|
||||||
|
name="kostengruppe_sonstiges"
|
||||||
|
placeholder="Sonstige"
|
||||||
|
for="invoice_type_other"
|
||||||
|
v-model="otherText"
|
||||||
|
@focus="invoiceType = 'other'"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</fieldset><br /><br />
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend><span style="font-weight: bolder;">Wie hoch ist der Betrag</span></legend>
|
||||||
|
<AmountInput v-model="amount" class="width-small" id="amount" name="amount" /> Euro
|
||||||
|
<info-icon></info-icon><br /><br />
|
||||||
|
|
||||||
|
<input
|
||||||
|
v-if="amount != '' && invoiceType !== null"
|
||||||
|
class="mareike-button"
|
||||||
|
onclick="document.getElementById('receipt').click();"
|
||||||
|
type="button"
|
||||||
|
value="Beleg auswählen und fortfahren" />
|
||||||
|
<input accept="application/pdf" type="file" id="receipt" name="receipt" @change="handleFileChange"
|
||||||
|
style="display: none"/>
|
||||||
|
</fieldset><br />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<PaymentData
|
||||||
|
v-if="finalStep"
|
||||||
|
:eventId="data.eventId"
|
||||||
|
:invoice-type="invoiceType"
|
||||||
|
:amount="amount"
|
||||||
|
:other-text="otherText"
|
||||||
|
:userName="data.userName"
|
||||||
|
:userEmail="data.userEmail"
|
||||||
|
:userTelephone="data.userTelephone"
|
||||||
|
:userAccountIban="data.userAccountIban"
|
||||||
|
:userAccountOwner="data.userAccountOwner"
|
||||||
|
:receipt="receipt"
|
||||||
|
travelReason=""
|
||||||
|
@close="finalStep = false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Modal from "../../../../../Views/Components/Modal.vue";
|
||||||
|
import IbanInput from "../../../../../Views/Components/IbanInput.vue";
|
||||||
|
import {useAjax} from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import TextResource from "../../../../../Views/Components/TextResource.vue";
|
||||||
|
import {invoiceCheckContactName} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import ErrorText from "../../../../../Views/Components/ErrorText.vue";
|
||||||
|
|
||||||
|
const { request } = useAjax();
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
eventId: Number,
|
||||||
|
invoiceType: String,
|
||||||
|
amount: [String, Number],
|
||||||
|
otherText: String,
|
||||||
|
receipt: File,
|
||||||
|
userName: String,
|
||||||
|
userEmail: String,
|
||||||
|
userTelephone: String,
|
||||||
|
userAccountOwner: String,
|
||||||
|
userAccountIban: String,
|
||||||
|
havePassengers: Number,
|
||||||
|
materialTransportation: Boolean,
|
||||||
|
travelReason: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const finalStep = ref(true)
|
||||||
|
const userName = ref(props.userName)
|
||||||
|
const userEmail = ref(props.userEmail)
|
||||||
|
const userTelephone = ref(props.userTelephone)
|
||||||
|
const userIban = ref('')
|
||||||
|
const userAccountOwner = ref('')
|
||||||
|
const paymentPurpose = ref('')
|
||||||
|
const sending = ref(false)
|
||||||
|
const success = ref(false)
|
||||||
|
const decision = ref('')
|
||||||
|
const errorMsg = ref('')
|
||||||
|
const confirmation = ref(null)
|
||||||
|
const receiptError = ref('')
|
||||||
|
|
||||||
|
async function sendData() {
|
||||||
|
if (!userName.value) return
|
||||||
|
|
||||||
|
sending.value = true
|
||||||
|
errorMsg.value = ''
|
||||||
|
success.value = false
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('name', userName.value)
|
||||||
|
formData.append('email', userEmail.value)
|
||||||
|
formData.append('telephone', userTelephone.value)
|
||||||
|
formData.append('amount', props.amount)
|
||||||
|
formData.append('otherText', props.otherText)
|
||||||
|
formData.append('decision', decision.value)
|
||||||
|
formData.append('accountOwner', userAccountOwner.value)
|
||||||
|
formData.append('accountIban', userIban.value)
|
||||||
|
formData.append('paymentPurpose', paymentPurpose.value)
|
||||||
|
formData.append('havePassengers', props.havePassengers ? 1 : 0)
|
||||||
|
formData.append('materialTransportation', props.materialTransportation ? 1 : 0)
|
||||||
|
formData.append('travelReason', props.travelReason)
|
||||||
|
|
||||||
|
if (props.receipt) {
|
||||||
|
formData.append('receipt', props.receipt)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request('/api/v1/invoice/new/' + props.eventId + '/' + props.invoiceType, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 'success') {
|
||||||
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
receiptError.value = response.message;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(result.message);
|
||||||
|
} finally {
|
||||||
|
sending.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :show="finalStep" title='Bitte gib deine Daten ein' @close="emit('close')" width="600px">
|
||||||
|
<label>
|
||||||
|
<strong>Dein Name Name (kein Pfadiname):</strong>
|
||||||
|
</label><br />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
@keyup="invoiceCheckContactName();"
|
||||||
|
id="contact_name"
|
||||||
|
name="contact_name" v-model="userName"
|
||||||
|
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||||
|
|
||||||
|
<label v-if="userName !== ''">
|
||||||
|
<strong>E-Mail-Adresse (Für Rückfragen):</strong>
|
||||||
|
</label><br />
|
||||||
|
<input
|
||||||
|
v-if="userName !== ''"
|
||||||
|
type="email"
|
||||||
|
name="contact_email"
|
||||||
|
v-model="userEmail"
|
||||||
|
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||||
|
|
||||||
|
<label v-if="userName !== ''">
|
||||||
|
<strong>Telefonnummer (für Rückfragen):</strong>
|
||||||
|
</label><br />
|
||||||
|
<input
|
||||||
|
v-if="userName !== ''"
|
||||||
|
type="text"
|
||||||
|
id="contact_telephone"
|
||||||
|
name="contact_telephone" v-model="userTelephone"
|
||||||
|
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||||
|
|
||||||
|
<span id="confirm_payment" v-if="userEmail !== '' && userTelephone !== ''">
|
||||||
|
<label>
|
||||||
|
<strong>Zahlungsempfänger*in:</strong>
|
||||||
|
</label><br />
|
||||||
|
<input type="text" name="account_owner" id="account_owner" v-model="userAccountOwner" style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<strong>IBAN:</strong>
|
||||||
|
</label><br />
|
||||||
|
<IbanInput id="account_iban" name="account_iban" v-model="userIban" style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<strong>Überweisungstext (laut Rechnung):</strong>
|
||||||
|
</label><br />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="payment_purpose"
|
||||||
|
name="payment_purpose"
|
||||||
|
v-model="paymentPurpose"
|
||||||
|
style="font-size: 14pt; width: 550px;"
|
||||||
|
/><br /><br />
|
||||||
|
<ErrorText :message="receiptError" />
|
||||||
|
|
||||||
|
<span v-if="paymentPurpose != '' && userAccountOwner != '' && userIban && userIban.length === 27"><br />
|
||||||
|
<input type="radio" name="confirmation_radio" value="payment" id="confirmation_radio_payment" v-model="confirmation">
|
||||||
|
<TextResource belongsTo="confirmation_radio_payment" textName="CONFIRMATION_DIRECT_PAYMENT" /><br /><br />
|
||||||
|
<input type="button" v-if="confirmation !== null && !sending" @click="sendData" value="Beleg einreichen" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* optional styling */
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import {useAjax} from "../../../../../../resources/js/components/ajaxHandler.js"
|
|||||||
import TextResource from "../../../../../Views/Components/TextResource.vue";
|
import TextResource from "../../../../../Views/Components/TextResource.vue";
|
||||||
import {invoiceCheckContactName} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
import {invoiceCheckContactName} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
||||||
import {toast} from "vue3-toastify";
|
import {toast} from "vue3-toastify";
|
||||||
|
import ErrorText from "../../../../../Views/Components/ErrorText.vue";
|
||||||
|
|
||||||
const { request } = useAjax();
|
const { request } = useAjax();
|
||||||
|
|
||||||
@@ -39,7 +40,8 @@ const success = ref(false)
|
|||||||
const decision = ref('')
|
const decision = ref('')
|
||||||
const errorMsg = ref('')
|
const errorMsg = ref('')
|
||||||
const confirmation = ref(null)
|
const confirmation = ref(null)
|
||||||
|
const receiptError = ref('')
|
||||||
|
const notices = ref('')
|
||||||
|
|
||||||
async function sendData() {
|
async function sendData() {
|
||||||
if (!userName.value) return
|
if (!userName.value) return
|
||||||
@@ -61,6 +63,7 @@ async function sendData() {
|
|||||||
formData.append('havePassengers', props.havePassengers ? 1 : 0)
|
formData.append('havePassengers', props.havePassengers ? 1 : 0)
|
||||||
formData.append('materialTransportation', props.materialTransportation ? 1 : 0)
|
formData.append('materialTransportation', props.materialTransportation ? 1 : 0)
|
||||||
formData.append('travelReason', props.travelReason)
|
formData.append('travelReason', props.travelReason)
|
||||||
|
formData.append('notices', notices.value)
|
||||||
|
|
||||||
if (props.receipt) {
|
if (props.receipt) {
|
||||||
formData.append('receipt', props.receipt)
|
formData.append('receipt', props.receipt)
|
||||||
@@ -75,6 +78,8 @@ async function sendData() {
|
|||||||
|
|
||||||
if (response.status === 'success') {
|
if (response.status === 'success') {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
receiptError.value = response.message;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(result.message);
|
toast.error(result.message);
|
||||||
@@ -87,7 +92,7 @@ async function sendData() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :show="finalStep" title='Bitte gib deine Daten ein' @close="emit('close')">
|
<Modal :show="finalStep" title='Bitte gib deine Daten ein' @close="emit('close')" width="600px">
|
||||||
<label>
|
<label>
|
||||||
<strong>Dein Name Name (kein Pfadiname):</strong>
|
<strong>Dein Name Name (kein Pfadiname):</strong>
|
||||||
</label><br />
|
</label><br />
|
||||||
@@ -95,8 +100,8 @@ async function sendData() {
|
|||||||
type="text"
|
type="text"
|
||||||
@keyup="invoiceCheckContactName();"
|
@keyup="invoiceCheckContactName();"
|
||||||
id="contact_name"
|
id="contact_name"
|
||||||
name="contact_name" v-model="userName"
|
class="refund-input"
|
||||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
name="contact_name" v-model="userName" /><br /><br />
|
||||||
|
|
||||||
<label v-if="userName !== ''">
|
<label v-if="userName !== ''">
|
||||||
<strong>E-Mail-Adresse (Für Rückfragen):</strong>
|
<strong>E-Mail-Adresse (Für Rückfragen):</strong>
|
||||||
@@ -104,9 +109,9 @@ async function sendData() {
|
|||||||
<input
|
<input
|
||||||
v-if="userName !== ''"
|
v-if="userName !== ''"
|
||||||
type="email"
|
type="email"
|
||||||
|
class="refund-input"
|
||||||
name="contact_email"
|
name="contact_email"
|
||||||
v-model="userEmail"
|
v-model="userEmail" /><br /><br />
|
||||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
|
||||||
|
|
||||||
<label v-if="userName !== ''">
|
<label v-if="userName !== ''">
|
||||||
<strong>Telefonnummer (für Rückfragen):</strong>
|
<strong>Telefonnummer (für Rückfragen):</strong>
|
||||||
@@ -114,17 +119,26 @@ async function sendData() {
|
|||||||
<input
|
<input
|
||||||
v-if="userName !== ''"
|
v-if="userName !== ''"
|
||||||
type="text"
|
type="text"
|
||||||
|
class="refund-input"
|
||||||
id="contact_telephone"
|
id="contact_telephone"
|
||||||
name="contact_telephone" v-model="userTelephone"
|
name="contact_telephone" v-model="userTelephone" /><br /><br />
|
||||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
|
||||||
|
|
||||||
<span id="decision" v-if="userEmail !== '' && decision === ''">
|
<label v-if="userName !== ''">
|
||||||
|
<strong>Weitere Informationen zur Abrechnung (Max. 128 Zeichen):</strong>
|
||||||
|
</label><br />
|
||||||
|
<textarea
|
||||||
|
id="notices"
|
||||||
|
class="refund-input"
|
||||||
|
name="notices" v-model="notices"
|
||||||
|
maxlength="128" /><br /><br />
|
||||||
|
|
||||||
|
<span id="decision" v-if="userEmail !== '' && userTelephone !== '' && decision === ''">
|
||||||
<label><br />
|
<label><br />
|
||||||
<strong>Möchtest du den Betrag spenden?</strong>
|
<strong>Möchtest du den Betrag spenden?</strong>
|
||||||
</label><br />
|
</label><br />
|
||||||
|
|
||||||
<input type="button" style="border-radius: 0; width: 100px;" @click="decision='donation'" value="Ja" />
|
<input type="button" class="refund-choice" @click="decision='donation'" value="Ja" />
|
||||||
<input type="button" style="border-radius: 0; width: 100px;" @click="decision='payout'" value="Nein" />
|
<input type="button" class="refund-choice" @click="decision='payout'" value="Nein" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span id="confirm_donation" v-if="decision === 'donation'">
|
<span id="confirm_donation" v-if="decision === 'donation'">
|
||||||
@@ -137,13 +151,13 @@ async function sendData() {
|
|||||||
<span id="confirm_payment" v-if="decision === 'payout'">
|
<span id="confirm_payment" v-if="decision === 'payout'">
|
||||||
<label>
|
<label>
|
||||||
<strong>Konto-Inhaber*in:</strong></label><br />
|
<strong>Konto-Inhaber*in:</strong></label><br />
|
||||||
<input type="text" name="account_owner" id="account_owner" v-model="userAccountOwner" style="font-size: 14pt; width: 550px;" /><br /><br />
|
<input type="text" name="account_owner" id="account_owner" class="refund-input" v-model="userAccountOwner" /><br /><br />
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<strong>IBAN:</strong>
|
<strong>IBAN:</strong>
|
||||||
</label><br />
|
</label><br />
|
||||||
<IbanInput id="account_iban" name="account_iban" v-model="userIban" style="font-size: 14pt; width: 550px;" /><br /><br />
|
<IbanInput id="account_iban" name="account_iban" class="refund-input" v-model="userIban" /><br /><br />
|
||||||
|
<ErrorText :message="receiptError" />
|
||||||
<span v-if="userAccountOwner != '' && userIban && userIban.length === 27"><br />
|
<span v-if="userAccountOwner != '' && userIban && userIban.length === 27"><br />
|
||||||
<input type="radio" name="confirmation_radio" value="payment" id="confirmation_radio_payment" v-model="confirmation">
|
<input type="radio" name="confirmation_radio" value="payment" id="confirmation_radio_payment" v-model="confirmation">
|
||||||
<TextResource belongsTo="confirmation_radio_payment" textName="CONFIRMATION_PAYMENT" /><br /><br />
|
<TextResource belongsTo="confirmation_radio_payment" textName="CONFIRMATION_PAYMENT" /><br /><br />
|
||||||
@@ -154,6 +168,26 @@ async function sendData() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* optional styling */
|
.refund-input {
|
||||||
</style>
|
font-size: 14pt;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 550px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refund-choice {
|
||||||
|
border-radius: 0;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.refund-input {
|
||||||
|
font-size: 12pt;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refund-choice {
|
||||||
|
width: calc(50% - 6px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class UserRegistrationCommand {
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
$generateActivationCoedeRequest = new GenerateActivationTokenRequest($user);
|
$generateActivationCoedeRequest = new GenerateActivationTokenRequest($user, new \DateTime()->add(new \DateInterval('P1D')));
|
||||||
$generateActivationCoedeDommand = new GenerateActivationTokenCommand($generateActivationCoedeRequest);
|
$generateActivationCoedeDommand = new GenerateActivationTokenCommand($generateActivationCoedeRequest);
|
||||||
$result = $generateActivationCoedeDommand->execute();
|
$result = $generateActivationCoedeDommand->execute();
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class LoginController extends CommonController {
|
|||||||
'password.required' => 'Bitte gib dein Passwort ein.',
|
'password.required' => 'Bitte gib dein Passwort ein.',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $this->users->findByUsername($request->get('username'));
|
$user = $this->users->findByUsername($request->input('username'));
|
||||||
if ($user !== null && $user->password === null) {
|
if ($user !== null && $user->password === null) {
|
||||||
return redirect()->intended('/register/verifyEmail');
|
return redirect()->intended('/register/verifyEmail');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,31 +27,31 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute
|
|||||||
|
|
||||||
function resetPassword() {
|
function resetPassword() {
|
||||||
window.location.href = '/reset-password';
|
window.location.href = '/reset-password';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppLayout title='Anmelden' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
<AppLayout title='Anmelden' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
<form method="POST" action="/login">
|
<form method="POST" action="/login">
|
||||||
<input type="hidden" name="_token" :value="csrfToken" />
|
<input type="hidden" name="_token" :value="csrfToken" />
|
||||||
<shadowed-box style="width: 50%; margin: 150px auto; padding: 20px;">
|
<shadowed-box class="login-box">
|
||||||
<table>
|
<table class="login-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Anmeldename</th>
|
<th>Anmeldename</th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="username" id="username"></input>
|
<input type="text" name="username" id="username" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Passwort</th>
|
<th>Passwort</th>
|
||||||
<td><input type="password" name="password" id="password"></input></td>
|
<td><input type="password" name="password" id="password" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" class="login-buttons">
|
||||||
<input type="submit" value="Anmelden" style="margin-top: 20px;" />
|
<input type="submit" value="Anmelden" />
|
||||||
<input type="button" @click="resetPassword" style="margin-top: 20px; margin-left: 20px;" value="Passwort vergessen" />
|
<input type="button" @click="resetPassword" value="Passwort vergessen" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -61,8 +61,59 @@ function resetPassword() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
th {
|
.login-table th {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
width: 50%;
|
||||||
|
margin: 150px auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-buttons input {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.login-box {
|
||||||
|
width: 70% !important;
|
||||||
|
margin: 80px auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smartphone */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.login-box {
|
||||||
|
width: 95% !important;
|
||||||
|
margin: 30px auto !important;
|
||||||
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-table,
|
||||||
|
.login-table tbody,
|
||||||
|
.login-table tr,
|
||||||
|
.login-table th,
|
||||||
|
.login-table td {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-table th {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-table th::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-buttons input {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ class InvoiceType extends CommonModel {
|
|||||||
|
|
||||||
public const INVOICE_TYPE_CATERING = 'catering';
|
public const INVOICE_TYPE_CATERING = 'catering';
|
||||||
|
|
||||||
|
public const INVOICE_TYPE_LOGSTIC = 'logistic';
|
||||||
|
|
||||||
|
public const INVOICE_TYPE_TECHNICAL = 'technical';
|
||||||
|
|
||||||
|
public const INVOICE_TYPE_MANAGEMENT = 'management';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'slug',
|
'slug',
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ class ProductionDataSeeder {
|
|||||||
$this->installSwimmingPermissions();
|
$this->installSwimmingPermissions();
|
||||||
$this->installEatingHabits();
|
$this->installEatingHabits();
|
||||||
$this->installFirstAidPermissions();
|
$this->installFirstAidPermissions();
|
||||||
$this->installTenants();
|
|
||||||
$this->installInvoiceMetaData();
|
$this->installInvoiceMetaData();
|
||||||
$this->installParticipationFeeTypes();
|
$this->installParticipationFeeTypes();
|
||||||
$this->installParticipationTypes();
|
$this->installParticipationTypes();
|
||||||
$this->installEfzStatus();
|
$this->installEfzStatus();
|
||||||
$this->installCronTasks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function installEfzStatus() {
|
private function installEfzStatus() {
|
||||||
@@ -102,21 +100,7 @@ class ProductionDataSeeder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function installTenants() {
|
|
||||||
Tenant::create([
|
|
||||||
'slug' => 'lv',
|
|
||||||
'name' => 'Landesunmittelbare Mitglieder',
|
|
||||||
'url' => 'mareike.local',
|
|
||||||
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V.',
|
|
||||||
'account_iban' => 'DE12345678901234567890',
|
|
||||||
'account_bic' => 'GENODEF1S10',
|
|
||||||
'email' => 'test@example.com',
|
|
||||||
'city' => 'Lommatzsch',
|
|
||||||
'postcode' => '01623',
|
|
||||||
'is_active_local_group' => true,
|
|
||||||
'has_active_instance' => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function installInvoiceMetaData() {
|
private function installInvoiceMetaData() {
|
||||||
InvoiceType::create([
|
InvoiceType::create([
|
||||||
@@ -155,12 +139,5 @@ class ProductionDataSeeder {
|
|||||||
CronTaskType::create(['slug' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
CronTaskType::create(['slug' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
||||||
CronTaskType::create(['slug' => CronTaskType::CRON_TASK_TYPE_DAILY]);
|
CronTaskType::create(['slug' => CronTaskType::CRON_TASK_TYPE_DAILY]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function installCronTasks() {
|
|
||||||
CronTask::create(['name' => 'UploadInvoices', 'execution_type' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
|
||||||
CronTask::create(['name' => 'CloseCostUnit', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:05']);
|
|
||||||
CronTask::create(['name' => 'CloseEvent', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:10']);
|
|
||||||
CronTask::create(['name' => 'NotifyTeam', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '20:00']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Installer\Providers;
|
||||||
|
|
||||||
|
use App\Enumerations\CronTaskType;
|
||||||
|
use App\Models\CronTask;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class BdpLvSachsenDataSeeder {
|
||||||
|
public function execute() {
|
||||||
|
$this->installTenants();
|
||||||
|
$this->installUsers();
|
||||||
|
$this->installCronTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installTenants()
|
||||||
|
{
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'lv',
|
||||||
|
'name' => 'Landesunmittelbare Mitglieder',
|
||||||
|
'url' => 'beta.mareike.sachsen.pfadfinden.de',
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V.',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'landesvorstand@sachsen.pfadfinden.de',
|
||||||
|
'email_finance' => 'finanzamt@sachsen.pfadfinden.de',
|
||||||
|
'city' => 'Limbach-Oberfrohne',
|
||||||
|
'postcode' => '09212',
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'wm',
|
||||||
|
'name' => 'Wilde Möhre',
|
||||||
|
'url' => 'wilde-moehre.mareike.local',
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Wilde Möhre',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'info@pfadfinden-halle.de',
|
||||||
|
'email_finance' => 'info@pfadfinden-halle.de',
|
||||||
|
'city' => 'Halle (Saale)',
|
||||||
|
'postcode' => '06120',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'anc',
|
||||||
|
'name' => 'Ancalagon',
|
||||||
|
'url' => 'ancalagon.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Wilde Möhre',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'info@stamm-ancalagon.de',
|
||||||
|
'email_finance' => 'info@stamm-ancalagon.de',
|
||||||
|
'city' => 'Leipzig',
|
||||||
|
'postcode' => '04425',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'ban',
|
||||||
|
'name' => 'Bankiva',
|
||||||
|
'url' => 'bankiva.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Wilde Möhre',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'bankiva@sachsen.pfadfinden.de',
|
||||||
|
'email_finance' => 'bankiva@sachsen.pfadfinden.de',
|
||||||
|
'city' => 'Leipzig',
|
||||||
|
'postcode' => '04157',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'ere',
|
||||||
|
'name' => 'Erebor',
|
||||||
|
'url' => 'erebor.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Wilde Möhre',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'freiberg@sachsen.pfadfinden.de',
|
||||||
|
'email_finance' => 'freiberg@sachsen.pfadfinden.de',
|
||||||
|
'city' => 'Freiberg',
|
||||||
|
'postcode' => '09599',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'fen',
|
||||||
|
'name' => 'Fennek',
|
||||||
|
'url' => 'fennek.mareike.local',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Fennek',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'chemnitz@sachsen.pfadfinden.de',
|
||||||
|
'email_finance' => 'chemnitz@sachsen.pfadfinden.de',
|
||||||
|
'city' => 'Chemnitz',
|
||||||
|
'postcode' => '09125',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'feu',
|
||||||
|
'name' => 'Feuerland',
|
||||||
|
'url' => 'feuerland.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Feuerland',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'info@stamm-feuerland.de',
|
||||||
|
'email_finance' => 'info@stamm-feuerland.de',
|
||||||
|
'city' => 'Leipzig',
|
||||||
|
'postcode' => '04318',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'gr',
|
||||||
|
'name' => 'Goldener Reiter',
|
||||||
|
'url' => 'goldener-reiter.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Goldener Reiter',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'stafue@goldenerreiter.org',
|
||||||
|
'email_finance' => 'stafue@goldenerreiter.org',
|
||||||
|
'city' => 'Dresden',
|
||||||
|
'postcode' => '01099',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'leo',
|
||||||
|
'name' => 'LEO',
|
||||||
|
'url' => 'leo.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Leo',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'stafue@leipzig-pfadfinden.de',
|
||||||
|
'email_finance' => 'stafue@leipzig-pfadfinden.de',
|
||||||
|
'city' => 'Leipzig',
|
||||||
|
'postcode' => '04229',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'rf',
|
||||||
|
'name' => 'Roter Fuchs',
|
||||||
|
'url' => 'roter-fuchs.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Roter Fuchs',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'info@pfadfinden-lommatzsch.de',
|
||||||
|
'email_finance' => 'info@pfadfinden-lommatzsch.de',
|
||||||
|
'city' => 'Lommatzsch',
|
||||||
|
'postcode' => '01623',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'sapi',
|
||||||
|
'name' => 'Saalepiraten',
|
||||||
|
'url' => 'saalepiraten.mareike.sachsen.pfadfinden.de',
|
||||||
|
'download_exports' => true,
|
||||||
|
'upload_exports' => true,
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Saalepiraten',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
|
'email' => 'saalepiratenbernburg@gmail.com',
|
||||||
|
'email_finance' => 'saalepiratenbernburg@gmail.com',
|
||||||
|
'city' => 'Bernburg',
|
||||||
|
'postcode' => '06408',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installUsers() {
|
||||||
|
$users = [
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'ada@irmielin.org', 'local_group' => 'feu', 'firstname' => 'Ada', 'lastname' => 'Lindner', 'email' => 'ada@irmielin.org', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'adrianhuebner1@gmx.de', 'local_group' => 'lv', 'firstname' => 'Adrian', 'lastname' => 'Hübner', 'email' => 'adrianhuebner1@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'aneta2622@seznam.cz', 'local_group' => 'fen', 'firstname' => 'Josef', 'lastname' => 'Soukup', 'email' => 'aneta2622@seznam.cz', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'antje.heilmann@gmx.de', 'local_group' => 'sapi', 'firstname' => 'Antje', 'lastname' => 'Heilmann', 'email' => 'antje.heilmann@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'arne.hartmann@leipzig-pfadfinden.de', 'local_group' => 'leo', 'firstname' => 'Arne', 'lastname' => 'Hartmann', 'email' => 'arne.hartmann@leipzig-pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_ADMINISTRATOR', 'user_role_local_group' => 'ROLE_GROUP_LEADER', 'username' => 'thomas@pfadfinden-halle.de', 'local_group' => 'wm', 'firstname' => 'Thomas', 'lastname' => 'Günther', 'email' => 'thomas@pfadfinden-halle.de', 'activation_token_expires_at' => null, 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'corinna-veith@gmx.de', 'local_group' => 'lv', 'firstname' => 'Corinna', 'lastname' => 'Lajewski', 'email' => 'corinna-veith@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'eliseschenfeld@gmail.com', 'local_group' => 'leo', 'firstname' => 'Elise', 'lastname' => 'Schenfeld', 'email' => 'eliseschenfeld@gmail.com', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'emmi.pins@posteo.de', 'local_group' => 'feu', 'firstname' => 'Emily', 'lastname' => 'Pins', 'email' => 'emmi.pins@posteo.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'fangorn@goldenerreiter.org', 'local_group' => 'gr', 'firstname' => 'Karl', 'lastname' => 'Redemann', 'email' => 'fangorn@goldenerreiter.org', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'franziskabaer@posteo.de', 'local_group' => 'fen', 'firstname' => 'Franziska', 'lastname' => 'Baer', 'email' => 'franziskabaer@posteo.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'frederik.pitz@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Frederik', 'lastname' => 'Pitz', 'email' => 'frederik.pitz@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'fyn.thaerigen@gmail.com', 'local_group' => 'lv', 'firstname' => 'Fyn Valentin', 'lastname' => 'Thärigen', 'email' => 'fyn.thaerigen@gmail.com', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'jakob.rudolph@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Jakob', 'lastname' => 'Rudolph', 'email' => 'jakob.rudolph@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'jenny.abel@proton.me', 'local_group' => 'fen', 'firstname' => 'Jenny', 'lastname' => 'Abel', 'email' => 'jenny.abel@proton.me', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'Jerry@pfadfinden-halle.de', 'local_group' => 'wm', 'firstname' => 'Jérémie', 'lastname' => 'Duckwitz', 'email' => 'Jerry@pfadfinden-halle.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'jonathan.helm@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Jonathan', 'lastname' => 'Helm', 'email' => 'jonathan.helm@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'jul-ka@gmx.net', 'local_group' => 'sapi', 'firstname' => 'Juliane', 'lastname' => 'Kahl', 'email' => 'jul-ka@gmx.net', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'julia.meier@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Julia', 'lastname' => 'Meier', 'email' => 'julia.meier@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'julika.scheibler@leipzig-pfadfinden.de','local_group' => 'leo', 'firstname' => 'Julika', 'lastname' => 'Scheibler', 'email' => 'julika.scheibler@leipzig-pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'karlmarx.95@gmail.com', 'local_group' => 'sapi', 'firstname' => 'Karlmarx', 'lastname' => 'Thangamani', 'email' => 'karlmarx.95@gmail.com', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'kilian@cyberfuchs.de', 'local_group' => 'lv', 'firstname' => 'Kilian', 'lastname' => 'Fuchs', 'email' => 'kilian@cyberfuchs.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'korni@pfadfinden-lommatzsch.de', 'local_group' => 'rf', 'firstname' => 'Konstantin', 'lastname' => 'Stephan', 'email' => 'korni@pfadfinden-lommatzsch.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'laiu@gmx.net', 'local_group' => 'lv', 'firstname' => 'Roukaya', 'lastname' => 'Zakarias', 'email' => 'laiu@gmx.net', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'lars.kristen@gmx.net', 'local_group' => 'ban', 'firstname' => 'Lars', 'lastname' => 'Kristen', 'email' => 'lars.kristen@gmx.net', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'laura.kopsch@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Laura', 'lastname' => 'Kopsch', 'email' => 'laura.kopsch@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'leopardgecko333@gmx.de', 'local_group' => 'lv', 'firstname' => 'Konrad', 'lastname' => 'Birch-Hirschfeld', 'email' => 'leopardgecko333@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'lionhartmann@yahoo.de', 'local_group' => 'wm', 'firstname' => 'Lion', 'lastname' => 'Hartmann', 'email' => 'lionhartmann@yahoo.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'lisa.sy@gmx.net', 'local_group' => 'lv', 'firstname' => 'Lisa', 'lastname' => 'Synnatzschke', 'email' => 'lisa.sy@gmx.net', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'lukas.dalpke@gmx.de', 'local_group' => 'gr', 'firstname' => 'Lukas', 'lastname' => 'Dalpke', 'email' => 'lukas.dalpke@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'marc.friede@gmx.de', 'local_group' => 'lv', 'firstname' => 'Marc', 'lastname' => 'Friede', 'email' => 'marc.friede@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'marcel.pfadfinden@familiebrause.de', 'local_group' => 'fen', 'firstname' => 'Marcel', 'lastname' => 'Brause', 'email' => 'marcel.pfadfinden@familiebrause.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'maria.hahn1991@gmx.de', 'local_group' => 'wm', 'firstname' => 'Clara Sophie', 'lastname' => 'Hahn', 'email' => 'maria.hahn1991@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'marlene.koehler@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Marlene', 'lastname' => 'Köhler', 'email' => 'marlene.koehler@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'marslisdneu@aol.de', 'local_group' => 'feu', 'firstname' => 'Steffi', 'lastname' => 'Deckenbach', 'email' => 'marslisdneu@aol.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'mascha.meyenschein@pfadfinden.de', 'local_group' => 'lv', 'firstname' => 'Mascha', 'lastname' => 'Meyenschein', 'email' => 'mascha.meyenschein@pfadfinden.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'nataliehammer22@web.de', 'local_group' => 'lv', 'firstname' => 'Nathalie', 'lastname' => 'Hammermüller', 'email' => 'nataliehammer22@web.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'pdeakova@centrum.cz', 'local_group' => 'fen', 'firstname' => 'Mikulas', 'lastname' => 'Loub', 'email' => 'pdeakova@centrum.cz', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'pfadfinden@christian-schlag.org', 'local_group' => 'wm', 'firstname' => 'Christian', 'lastname' => 'Schlag', 'email' => 'pfadfinden@christian-schlag.org', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'rebekka.thielen@googlemail.com', 'local_group' => 'lv', 'firstname' => 'Almuna Elisa', 'lastname' => 'Hines', 'email' => 'rebekka.thielen@googlemail.com', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'rieke.langhans@gmail.com', 'local_group' => 'lv', 'firstname' => 'Rieke', 'lastname' => 'Langhans', 'email' => 'rieke.langhans@gmail.com', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'rolle@tocraft.dev', 'local_group' => 'anc', 'firstname' => 'Tobias', 'lastname' => 'Cuiper', 'email' => 'rolle@tocraft.dev', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'ronja.opel@icloud.com', 'local_group' => 'leo', 'firstname' => 'Ronja', 'lastname' => 'Opel', 'email' => 'ronja.opel@icloud.com', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'schubert-steffi83@web.de', 'local_group' => 'fen', 'firstname' => 'Fino Kilian', 'lastname' => 'Schubert', 'email' => 'schubert-steffi83@web.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'sebastianeckloff@gmx.de', 'local_group' => 'lv', 'firstname' => 'Sebastian', 'lastname' => 'Eckloff', 'email' => 'sebastianeckloff@gmx.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'sonne.grazek@yahoo.de', 'local_group' => 'feu', 'firstname' => 'Mandy', 'lastname' => 'Grazek', 'email' => 'sonne.grazek@yahoo.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'flieder.pfadfinden@familiebrause.de', 'local_group' => 'fen', 'firstname' => 'Sophie', 'lastname' => 'Brause', 'email' => 'flieder.pfadfinden@familiebrause.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'Theresa.grafe@posteo.de', 'local_group' => 'lv', 'firstname' => 'Theresa', 'lastname' => 'Grafe', 'email' => 'Theresa.grafe@posteo.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_ADMINISTRATOR', 'user_role_local_group' => 'ROLE_USER', 'username' => 'tux@goldenerreiter.org', 'local_group' => 'gr', 'firstname' => 'Christoph', 'lastname' => 'Wagner', 'email' => 'tux@goldenerreiter.org', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'undine.reschke@web.de', 'local_group' => 'lv', 'firstname' => 'Undine', 'lastname' => 'Reschke', 'email' => 'undine.reschke@web.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
['user_role_main' => 'ROLE_USER', 'user_role_local_group' => 'ROLE_USER', 'username' => 'yannik@suck-taucha.de', 'local_group' => 'lv', 'firstname' => 'Yannik', 'lastname' => 'Suck', 'email' => 'yannik@suck-taucha.de', 'activation_token_expires_at' => '2026-02-20 21:53:58', 'active' => true],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($users as $userData) {
|
||||||
|
User::create($userData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installCronTasks() {
|
||||||
|
CronTask::create(['name' => 'UploadInvoices', 'execution_type' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
||||||
|
CronTask::create(['name' => 'CloseCostUnit', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:05']);
|
||||||
|
CronTask::create(['name' => 'CloseEvent', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:10']);
|
||||||
|
CronTask::create(['name' => 'NotifyTeam', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '20:00']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,8 @@ class activationCodeTemplate extends MessageTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function composeMessage(EmailAddress $emailAddress, string $activationCode): void {
|
public function composeMessage(EmailAddress $emailAddress, string $activationCode): void {
|
||||||
$this->message = "Dein Aktivierungscode lautet: {$activationCode}" . PHP_EOL .
|
$this->message = "Hallo,<br >dein angefordertet Aktivierungscode lautet: {$activationCode}<br />.
|
||||||
"Gib diesen zusammen mit der Mailadresse {$emailAddress->getValue()} ein.";
|
Gib diesen zusammen mit der Mailadresse {$emailAddress->getValue()} ein.<br /><br />
|
||||||
|
Hast du keinen Code angefordert?<br />In diesem Fall kannst du die E-Mail ignorieren.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Scopes\InstancedModel;
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $name
|
* @property string $name
|
||||||
@@ -44,7 +45,15 @@ class CostUnit extends InstancedModel
|
|||||||
return $this->hasMany(Invoice::class);
|
return $this->hasMany(Invoice::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function estimates() : hasMany {
|
||||||
|
return $this->hasMany(CostUnitEstimate::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function tenant() : BelongsTo {
|
public function tenant() : BelongsTo {
|
||||||
return $this->belongsTo(Tenant::class, 'tenant', 'slug');
|
return $this->belongsTo(Tenant::class, 'tenant', 'slug');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function event() : HasOne {
|
||||||
|
return $this->hasOne(Event::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Casts\AmountCast;
|
||||||
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Resources\EventResource;
|
||||||
|
use App\Scopes\InstancedModel;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
|
||||||
|
class CostUnitEstimate extends InstancedModel
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant',
|
||||||
|
'cost_unit_id',
|
||||||
|
'type',
|
||||||
|
'description',
|
||||||
|
'flat_amount',
|
||||||
|
'amount_by_user',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'flat_amount' => AmountCast::class,
|
||||||
|
'amount_by_user' => AmountCast::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function costUnit() : BelongsTo{
|
||||||
|
return $this->belongsTo(CostUnit::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invoiceType() : InvoiceType {
|
||||||
|
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function calculateAmount() : ?Amount {
|
||||||
|
switch (true) {
|
||||||
|
case $this->flat_amount !== null:
|
||||||
|
return $this->flat_amount;
|
||||||
|
default:
|
||||||
|
$event = $this->costUnit()->first()->event()?->first();
|
||||||
|
if (null !== $event) {
|
||||||
|
$participants = $event->participants()->count();
|
||||||
|
$amount = clone($this->amount_by_user);
|
||||||
|
return $amount->multiply($participants);
|
||||||
|
} else {
|
||||||
|
return $this->amount_by_user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Scopes\CommonModel;
|
use App\Scopes\CommonModel;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
|
||||||
|
|
||||||
class CronTask extends CommonModel
|
class CronTask extends CommonModel
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,6 @@ class CronTask extends CommonModel
|
|||||||
protected $dates = ['last_run'];
|
protected $dates = ['last_run'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'last_run' => 'datetime',
|
'last_run' => AsArrayObject::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class EventParticipant extends InstancedModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getFullName() : string {
|
public function getFullName() : string {
|
||||||
if ($this->nickname === null) {
|
if ($this->nickname === null || trim($this->nickname) === '') {
|
||||||
return sprintf('%1$s %2$s', $this->firstname, $this->lastname)
|
return sprintf('%1$s %2$s', $this->firstname, $this->lastname)
|
||||||
|>trim(...);
|
|>trim(...);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Invoice extends InstancedModel
|
|||||||
'contact_phone',
|
'contact_phone',
|
||||||
'contact_bank_owner',
|
'contact_bank_owner',
|
||||||
'contact_bank_iban',
|
'contact_bank_iban',
|
||||||
|
'payment_purpose',
|
||||||
'amount',
|
'amount',
|
||||||
'donation',
|
'donation',
|
||||||
'distance',
|
'distance',
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class CronTaskHandleProvider extends CommonController
|
|||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
|
|
||||||
$tenants = Tenant::where('has_active_instance', true)->get();
|
$tenants = Tenant::where('has_active_instance', true)->get();
|
||||||
|
|
||||||
foreach ($tenants as $tenant) {
|
foreach ($tenants as $tenant) {
|
||||||
app()->instance('tenant', $tenant);
|
app()->instance('tenant', $tenant);
|
||||||
$this->runTenantTasks($tenant, $now);
|
$this->runTenantTasks($tenant, $now);
|
||||||
@@ -31,7 +30,6 @@ class CronTaskHandleProvider extends CommonController
|
|||||||
|
|
||||||
private function runTenantTasks(Tenant $tenant, Carbon $now) {
|
private function runTenantTasks(Tenant $tenant, Carbon $now) {
|
||||||
$tasks = CronTask::all();
|
$tasks = CronTask::all();
|
||||||
|
|
||||||
foreach ($tasks as $task) {
|
foreach ($tasks as $task) {
|
||||||
|
|
||||||
// --- Every-Time Tasks ---
|
// --- Every-Time Tasks ---
|
||||||
@@ -41,10 +39,20 @@ class CronTaskHandleProvider extends CommonController
|
|||||||
|
|
||||||
// --- Daily Tasks ---
|
// --- Daily Tasks ---
|
||||||
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_DAILY) {
|
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_DAILY) {
|
||||||
$scheduledTime = $task->schedule_time;
|
$tenantLastRun = $task->last_run;
|
||||||
$alreadyRunToday = $task->last_run?->isToday() ?? false;
|
|
||||||
|
|
||||||
if (!$alreadyRunToday && $now->format('H:i') === $scheduledTime) {
|
if ($tenantLastRun !== null) {
|
||||||
|
$tenantLastRun = Carbon::parse($tenantLastRun[$tenant->slug]) ?? null;
|
||||||
|
} else {
|
||||||
|
$tenantLastRun = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTime = \DateTime::createFromFormat('Y-m-d H:i:s', date('Y-m-d ') . $task->schedule_time);
|
||||||
|
$now = Carbon::now();
|
||||||
|
|
||||||
|
$alreadyRunToday = $tenantLastRun == null ? false : $tenantLastRun->isToday() ?? false;
|
||||||
|
|
||||||
|
if (!$alreadyRunToday && $now >= $scheduledTime) {
|
||||||
$this->runTask($task);
|
$this->runTask($task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,14 +64,18 @@ class CronTaskHandleProvider extends CommonController
|
|||||||
$logger = $this->taskLogger($task->name, app('tenant'));
|
$logger = $this->taskLogger($task->name, app('tenant'));
|
||||||
app()->instance('taskLogger', $logger);
|
app()->instance('taskLogger', $logger);
|
||||||
|
|
||||||
|
|
||||||
$taskClass = "\\App\\Tasks\\" . $task->name;
|
$taskClass = "\\App\\Tasks\\" . $task->name;
|
||||||
if (class_exists($taskClass)) {
|
if (class_exists($taskClass)) {
|
||||||
$instance = new $taskClass();
|
$instance = new $taskClass();
|
||||||
$instance->handle();
|
$instance->handle();
|
||||||
|
$lastRun = $task->last_run;
|
||||||
|
if ($lastRun === null) {
|
||||||
|
$lastRun = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastRun[app('tenant')->slug] = now();
|
||||||
// Update last_run
|
// Update last_run
|
||||||
$task->last_run = now();
|
$task->last_run = $lastRun;
|
||||||
$task->save();
|
$task->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ class GlobalDataProvider {
|
|||||||
'navbar' => $this->generateNavbar(),
|
'navbar' => $this->generateNavbar(),
|
||||||
'tenant' => app('tenant'),
|
'tenant' => app('tenant'),
|
||||||
'activeUsers' => $this->getActiveUsers(),
|
'activeUsers' => $this->getActiveUsers(),
|
||||||
|
'version' => config('app.version'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllInvoiceTypes() : JsonResponse {
|
public function getAllInvoiceTypes() : JsonResponse {
|
||||||
$invoiceTypes = [];
|
$invoiceTypes = [];
|
||||||
foreach (InvoiceType::all() as $invoiceType) {
|
foreach (InvoiceType::orderBy('sort_order')->get() as $invoiceType) {
|
||||||
if (
|
if (
|
||||||
$invoiceType->slug === InvoiceType::INVOICE_TYPE_OTHER
|
$invoiceType->slug === InvoiceType::INVOICE_TYPE_OTHER
|
||||||
) {
|
) {
|
||||||
@@ -52,10 +53,9 @@ class GlobalDataProvider {
|
|||||||
|
|
||||||
public function getInvoiceTypes() : JsonResponse {
|
public function getInvoiceTypes() : JsonResponse {
|
||||||
$invoiceTypes = [];
|
$invoiceTypes = [];
|
||||||
foreach (InvoiceType::all() as $invoiceType) {
|
foreach (InvoiceType::orderBy('sort_order')->get() as $invoiceType) {
|
||||||
if (
|
if (
|
||||||
$invoiceType->slug === InvoiceType::INVOICE_TYPE_TRAVELLING ||
|
$invoiceType->slug === InvoiceType::INVOICE_TYPE_TRAVELLING
|
||||||
$invoiceType->slug === InvoiceType::INVOICE_TYPE_OTHER
|
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -66,8 +66,6 @@ class GlobalDataProvider {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$invoiceTypes[] = ['slug' => InvoiceType::INVOICE_TYPE_OTHER, 'name' => 'Sonstige Kosten'];
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'invoiceTypes' => $invoiceTypes
|
'invoiceTypes' => $invoiceTypes
|
||||||
]);
|
]);
|
||||||
@@ -110,6 +108,7 @@ class GlobalDataProvider {
|
|||||||
'common' => [],
|
'common' => [],
|
||||||
'costunits' => [],
|
'costunits' => [],
|
||||||
'events' => [],
|
'events' => [],
|
||||||
|
'eventControl' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
$navigation['personal'][] = ['url' => '/', 'display' => 'Home'];
|
$navigation['personal'][] = ['url' => '/', 'display' => 'Home'];
|
||||||
@@ -129,7 +128,8 @@ class GlobalDataProvider {
|
|||||||
$navigation['events'][] = ['url' => '/event/details/' . $event->identifier, 'display' => $event->name];
|
$navigation['events'][] = ['url' => '/event/details/' . $event->identifier, 'display' => $event->name];
|
||||||
}
|
}
|
||||||
|
|
||||||
$navigation['events'][] = ['url' => '/event/new', 'display' => 'Neue Veranstaltung'];
|
$navigation['eventControl'][] = ['url' => '/archived-events', 'display' => 'Archivierte Veranstaltungen'];
|
||||||
|
$navigation['eventControl'][] = ['url' => '/create-event', 'display' => 'Neue Veranstaltung'];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class InvoiceCsvFileProvider {
|
|||||||
'"Kontoinhaber*in"',
|
'"Kontoinhaber*in"',
|
||||||
'"Betrag"',
|
'"Betrag"',
|
||||||
'"Spende"',
|
'"Spende"',
|
||||||
'"Beleg ohne Auszahlung"',
|
'"Buchungstext"'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($this->invoices as $invoice) {
|
foreach ($this->invoices as $invoice) {
|
||||||
@@ -38,7 +38,7 @@ class InvoiceCsvFileProvider {
|
|||||||
'"' . $invoiceReadable['accountOwner'] . '"',
|
'"' . $invoiceReadable['accountOwner'] . '"',
|
||||||
'"' . $invoiceReadable['amountPlain'] . '"',
|
'"' . $invoiceReadable['amountPlain'] . '"',
|
||||||
'"' . $invoiceReadable['donation'] . '"',
|
'"' . $invoiceReadable['donation'] . '"',
|
||||||
'"' . $invoiceReadable['alreadyPaid'] . '"'
|
'"' . $invoiceReadable['paymentPurpose'] . '"'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$csvArray[] = $csvLine;
|
$csvArray[] = $csvLine;
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
|
use App\Resources\InvoiceResource;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class PainFileProvider {
|
class PainFileProvider {
|
||||||
public string $senderIban;
|
public string $senderIban;
|
||||||
@@ -89,6 +91,8 @@ class PainFileProvider {
|
|||||||
$pmt_inf->appendChild($dbtr_agt);
|
$pmt_inf->appendChild($dbtr_agt);
|
||||||
|
|
||||||
foreach ($this->invoices as $index => $invoice) {
|
foreach ($this->invoices as $index => $invoice) {
|
||||||
|
$invoiceResource = new InvoiceResource($invoice)->toArray(new Request());
|
||||||
|
|
||||||
$cdt_trf_tx_inf = $doc->createElement('CdtTrfTxInf');
|
$cdt_trf_tx_inf = $doc->createElement('CdtTrfTxInf');
|
||||||
|
|
||||||
$pmt_id = $doc->createElement('PmtId');
|
$pmt_id = $doc->createElement('PmtId');
|
||||||
@@ -112,7 +116,7 @@ class PainFileProvider {
|
|||||||
$cdt_trf_tx_inf->appendChild($cdtr_acct);
|
$cdt_trf_tx_inf->appendChild($cdtr_acct);
|
||||||
|
|
||||||
$rmt_inf = $doc->createElement('RmtInf');
|
$rmt_inf = $doc->createElement('RmtInf');
|
||||||
$rmt_inf->appendChild($doc->createElement('Ustrd', 'Auslagenerstattung Rechnungsnummer ' . $invoice['invoice_number']));
|
$rmt_inf->appendChild($doc->createElement('Ustrd', $invoiceResource['paymentPurpose']));
|
||||||
$cdt_trf_tx_inf->appendChild($rmt_inf);
|
$cdt_trf_tx_inf->appendChild($rmt_inf);
|
||||||
|
|
||||||
$pmt_inf->appendChild($cdt_trf_tx_inf);
|
$pmt_inf->appendChild($cdt_trf_tx_inf);
|
||||||
|
|||||||
@@ -16,12 +16,6 @@ class WebDavProvider {
|
|||||||
public function uploadFile(string $fileName) : bool {
|
public function uploadFile(string $fileName) : bool {
|
||||||
$baseDir = storage_path('app/private/');
|
$baseDir = storage_path('app/private/');
|
||||||
|
|
||||||
return $this->webDavClient->upload_file($baseDir . $fileName, $this->workingDirectory . '/'.
|
return $this->webDavClient->upload_file($baseDir . $fileName, $this->workingDirectory . '/');
|
||||||
basename($fileName)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ class CostUnitRepository {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function getCurrentEvents() : array {
|
public function getCurrentEvents(bool $disableAccessCheck = false) : array {
|
||||||
return $this->getCostUnitsByCriteria([
|
return $this->getCostUnitsByCriteria([
|
||||||
'allow_new' => true,
|
'allow_new' => true,
|
||||||
'type' => CostUnitType::COST_UNIT_TYPE_EVENT,
|
'type' => CostUnitType::COST_UNIT_TYPE_EVENT,
|
||||||
'archived' => false
|
'archived' => false
|
||||||
]);
|
],
|
||||||
|
true,
|
||||||
|
$disableAccessCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRunningJobs() : array {
|
public function getRunningJobs() : array {
|
||||||
@@ -90,7 +92,7 @@ class CostUnitRepository {
|
|||||||
$visibleCostUnits = [];
|
$visibleCostUnits = [];
|
||||||
/** @var CostUnit $costUnit */
|
/** @var CostUnit $costUnit */
|
||||||
foreach (Costunit::where($criteria)->get() as $costUnit) {
|
foreach (Costunit::where($criteria)->get() as $costUnit) {
|
||||||
if ($costUnit->treasurers()->where('user_id', $user->id)->exists() || $canSeeAll || $disableAccessCheck) {
|
if ($canSeeAll || $disableAccessCheck || $costUnit->treasurers()->where('user_id', $user->id)->exists() ) {
|
||||||
if ($forDisplay) {
|
if ($forDisplay) {
|
||||||
$visibleCostUnits[] = new CostUnitResource($costUnit)->toArray(request());
|
$visibleCostUnits[] = new CostUnitResource($costUnit)->toArray(request());
|
||||||
} else {
|
} else {
|
||||||
@@ -176,6 +178,18 @@ class CostUnitRepository {
|
|||||||
return $amount;
|
return $amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sumupEstimatedByInvoiceType(CostUnit $costUnit, InvoiceType $invoiceType) : Amount {
|
||||||
|
$amount = new Amount(0, 'Euro');
|
||||||
|
foreach ($costUnit->estimates()->get() as $estimate) {
|
||||||
|
if ($estimate->type !== $invoiceType->slug) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount->addAmount($estimate->calculateAmount());
|
||||||
|
}
|
||||||
|
return $amount;
|
||||||
|
}
|
||||||
|
|
||||||
public function sumupUnhandledAmounts(CostUnit $costUnit, bool $donatedAmount = false) : Amount {
|
public function sumupUnhandledAmounts(CostUnit $costUnit, bool $donatedAmount = false) : Amount {
|
||||||
$amount = new Amount(0, '');
|
$amount = new Amount(0, '');
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\CostUnit;
|
||||||
|
use App\Models\CostUnitEstimate;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
|
class EstimatesRepository {
|
||||||
|
public function getEstimates(CostUnit $costUnit, string $estimateType) : array {
|
||||||
|
$return = [];
|
||||||
|
foreach ($costUnit->estimates()->where('type', $estimateType)->get() as $estimate) {
|
||||||
|
$return[] = $estimate->toResource()->toArray(request());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById(int $estimateId, bool $accessCheck = true) : ?CostUnitEstimate {
|
||||||
|
$estimate = CostUnitEstimate::find($estimateId);
|
||||||
|
if ($estimate === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($accessCheck) {
|
||||||
|
$costUnitRepository = new CostUnitRepository();
|
||||||
|
if (null === $costUnitRepository->getById($estimate->cost_unit_id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $estimate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalAmount(CostUnit $costUnit, string $estimateType) : Amount {
|
||||||
|
$total = new Amount(0, 'Euro');
|
||||||
|
foreach ($costUnit->estimates()->where('type', $estimateType)->get() as $estimate) {
|
||||||
|
$total->addAmount($estimate->calculateAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -88,7 +88,7 @@ class EventParticipantRepository {
|
|||||||
|
|
||||||
public function getParticipantsWithIntolerances(Event $event, Request $request) : array {
|
public function getParticipantsWithIntolerances(Event $event, Request $request) : array {
|
||||||
$participants = [];
|
$participants = [];
|
||||||
foreach ($event->participants()->whereNotNull('intolerances')->whereNot('intolerances' , '=', '')->get() as $participant) {
|
foreach ($event->participants()->orderBy('lastname')->orderBy('firstname')->whereNotNull('intolerances')->whereNot('intolerances' , '=', '')->get() as $participant) {
|
||||||
$participants[] = $participant->toResource()->toArray($request);
|
$participants[] = $participant->toResource()->toArray($request);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ class EventParticipantRepository {
|
|||||||
|
|
||||||
public function getKitchenOverview(Event $event) : array {
|
public function getKitchenOverview(Event $event) : array {
|
||||||
$data = [];
|
$data = [];
|
||||||
$participants = $event->participants()->get();
|
$participants = $event->participants()->orderBy('lastname')->orderBy('firstname')->get();
|
||||||
|
|
||||||
|
|
||||||
for ($cur_date = $event->start_date; $cur_date <= $event->end_date; $cur_date->modify('+1 day')) {
|
for ($cur_date = $event->start_date; $cur_date <= $event->end_date; $cur_date->modify('+1 day')) {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class EventRepository {
|
|||||||
|
|
||||||
$visibleEvents = [];
|
$visibleEvents = [];
|
||||||
/** @var Event $event */
|
/** @var Event $event */
|
||||||
foreach (Event::where($criteria)->get() as $event) {
|
foreach (Event::where($criteria)->orderBy('start_date')->get() as $event) {
|
||||||
|
|
||||||
if ($canSeeAll || !$accessCheck || $event->eventManagers()->where('user_id', $user->id)->exists()) {
|
if ($canSeeAll || !$accessCheck || $event->eventManagers()->where('user_id', $user->id)->exists()) {
|
||||||
$visibleEvents[] = $event;
|
$visibleEvents[] = $event;
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class UserRepository {
|
|||||||
'first_aid_permission' => $request->firstAidPermission,
|
'first_aid_permission' => $request->firstAidPermission,
|
||||||
'bank_account_owner' => $request->bankAccountOwner,
|
'bank_account_owner' => $request->bankAccountOwner,
|
||||||
'bank_account_iban' => $request->bankAccountIban,
|
'bank_account_iban' => $request->bankAccountIban,
|
||||||
|
'birthday' => $request->birthday,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Resources;
|
||||||
|
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Enumerations\EfzStatus;
|
||||||
|
use App\Enumerations\ParticipationType;
|
||||||
|
use App\Models\CostUnitEstimate;
|
||||||
|
use App\Models\EventParticipant;
|
||||||
|
use App\ValueObjects\Age;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class CostUnitEstimateResource extends JsonResource
|
||||||
|
{
|
||||||
|
function __construct(CostUnitEstimate $estimate)
|
||||||
|
{
|
||||||
|
parent::__construct($estimate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray($request) : array
|
||||||
|
{
|
||||||
|
$amount = $this->resource->calculateAmount();
|
||||||
|
$singleAmountString = $this->resource->flat_amount?->toString();
|
||||||
|
$amountType = 'flat';
|
||||||
|
if ($singleAmountString === null) {
|
||||||
|
$amountType = 'per_person';
|
||||||
|
$singleAmountString = $this->resource->amount_by_user->toString() . ' / Person (' . $amount->toString() . ' Gesamt)';
|
||||||
|
} else {
|
||||||
|
$singleAmountString .= ' Gesamt';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $this->resource->id,
|
||||||
|
'title' => $this->resource->description,
|
||||||
|
'singleAmountString' => $singleAmountString,
|
||||||
|
'calculatedAmount' => $amount,
|
||||||
|
'calculatedAmountString' => $amount->toString(),
|
||||||
|
'amountValue' => $amount->getAmount(),
|
||||||
|
'amountType' => $amountType,
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,10 +31,15 @@ class CostUnitResource {
|
|||||||
|
|
||||||
$amounts = [];
|
$amounts = [];
|
||||||
$overAllAmount = new Amount(0, 'Euro');
|
$overAllAmount = new Amount(0, 'Euro');
|
||||||
foreach (InvoiceType::all() as $invoiceType) {
|
$overAllEstimatedAmount = new Amount(0, 'Euro');
|
||||||
|
foreach (InvoiceType::orderBy('sort_order')->get() as $invoiceType) {
|
||||||
$overAllAmount->addAmount($costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType));
|
$overAllAmount->addAmount($costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType));
|
||||||
|
$overAllEstimatedAmount->addAmount($costUnitRepository->sumupEstimatedByInvoiceType($this->costUnit, $invoiceType));
|
||||||
$amounts[$invoiceType->slug]['string'] = $costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType)->toString();
|
$amounts[$invoiceType->slug]['string'] = $costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType)->toString();
|
||||||
$amounts[$invoiceType->slug]['name'] = $invoiceType->name;
|
$amounts[$invoiceType->slug]['name'] = $invoiceType->name;
|
||||||
|
$amounts[$invoiceType->slug]['estimated'] = $costUnitRepository->sumupEstimatedByInvoiceType($this->costUnit, $invoiceType);
|
||||||
|
$amounts[$invoiceType->slug]['estimatedString'] = $costUnitRepository->sumupEstimatedByInvoiceType($this->costUnit, $invoiceType)->toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -52,6 +57,7 @@ class CostUnitResource {
|
|||||||
'treasurers' => $this->costUnit->treasurers()->get()->map(fn($user) => new UserResource($user))->toArray(),
|
'treasurers' => $this->costUnit->treasurers()->get()->map(fn($user) => new UserResource($user))->toArray(),
|
||||||
'amounts' => $amounts,
|
'amounts' => $amounts,
|
||||||
'overAllAmount' => ['text' => $overAllAmount->toString(), 'value' => $overAllAmount],
|
'overAllAmount' => ['text' => $overAllAmount->toString(), 'value' => $overAllAmount],
|
||||||
|
'overAllEstimatedAmount' => ['text' => $overAllEstimatedAmount->toString(), 'value' => $overAllEstimatedAmount],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,11 @@ class EventParticipantResource extends JsonResource
|
|||||||
'unregisteredAt' => $this->resource->unregistered_at?->format('d.m.Y'),
|
'unregisteredAt' => $this->resource->unregistered_at?->format('d.m.Y'),
|
||||||
'fullname' => $this->resource->getFullName(),
|
'fullname' => $this->resource->getFullName(),
|
||||||
'age' => new Age($this->resource->birthday)->getAge(),
|
'age' => new Age($this->resource->birthday)->getAge(),
|
||||||
'localgroup' => $this->resource->localGroup()->first()->name,
|
'localgroup' => $this->resource->localGroup()?->first()?->name ?? 'Nicht im LV',
|
||||||
'swimmingPermission' => $this->resource->swimmingPermission()->first()->short,
|
'swimmingPermission' => $this->resource->swimmingPermission()->first()->short,
|
||||||
'extendedFirstAid' => $this->resource->firstAidPermission()->first()->name,
|
'extendedFirstAid' => $this->resource->firstAidPermission()->first()->name,
|
||||||
'tetanusVaccination' => $this->resource->tetanus_vaccination?->format('d.m.Y') ?? 'Unbekannt',
|
'tetanusVaccination' => $this->resource->tetanus_vaccination?->format('d.m.Y') ?? 'Unbekannt',
|
||||||
|
'tetanusVaccinationEdit' => $this->resource->tetanus_vaccination?->format('Y-m-d') ?? null,
|
||||||
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
|
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
|
||||||
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name,
|
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name,
|
||||||
'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct && $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
|
'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct && $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
|
||||||
@@ -64,10 +65,10 @@ class EventParticipantResource extends JsonResource
|
|||||||
'amountPaid' => ['value' => $this->resource->amount_paid, 'readable' => $this->resource->amount_paid?->toString() ?? '0,00 Euro', 'short' => $this->resource->amount_paid?->getFormattedAmount() ?? '0,00'],
|
'amountPaid' => ['value' => $this->resource->amount_paid, 'readable' => $this->resource->amount_paid?->toString() ?? '0,00 Euro', 'short' => $this->resource->amount_paid?->getFormattedAmount() ?? '0,00'],
|
||||||
'amountExpected' => ['value' => $this->resource->amount, 'readable' => $this->resource->amount?->toString() ?? '0,00 Euro', 'short' => $this->resource->amount?->getFormattedAmount() ?? '0,00'],
|
'amountExpected' => ['value' => $this->resource->amount, 'readable' => $this->resource->amount?->toString() ?? '0,00 Euro', 'short' => $this->resource->amount?->getFormattedAmount() ?? '0,00'],
|
||||||
'alcoholicsAllowed' => new Age($this->resource->birthday)->getAge() >= $event->alcoholics_age,
|
'alcoholicsAllowed' => new Age($this->resource->birthday)->getAge() >= $event->alcoholics_age,
|
||||||
'localGroupPostcode' => $this->resource->localGroup()->first()->postcode,
|
'localGroupPostcode' => $this->resource->localGroup()->first()?->postcode ?? '00000',
|
||||||
'localGroupCity' => $this->resource->localGroup()->first()->city,
|
'localGroupCity' => $this->resource->localGroup()->first()?->city ?? '00000',
|
||||||
'state' => config('postCode.map.' . $this->resource->postcode),
|
'state' => config('postCode.map.' . $this->resource->postcode),
|
||||||
'localGroupState' => config('postCode.map.' . $this->resource->localGroup()->first()->postcode),
|
'localGroupState' => null !== $this->resource->localGroup()->first()?->postcode ? config('postCode.map.' . $this->resource->postcode) : '--',
|
||||||
'birthday' => $this->resource->birthday->format('d.m.Y'),
|
'birthday' => $this->resource->birthday->format('d.m.Y'),
|
||||||
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name,
|
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name,
|
||||||
'cocColor' => match ($this->resource->efz_status) {
|
'cocColor' => match ($this->resource->efz_status) {
|
||||||
|
|||||||
@@ -26,16 +26,16 @@ class EventResource extends JsonResource{
|
|||||||
'name' => $this->event->name,
|
'name' => $this->event->name,
|
||||||
'identifier' => $this->event->identifier,
|
'identifier' => $this->event->identifier,
|
||||||
'url' => 'https://' . app('tenant')->url . '/event/' . $this->event->identifier . '/signup',
|
'url' => 'https://' . app('tenant')->url . '/event/' . $this->event->identifier . '/signup',
|
||||||
|
'urlShort' => 'https://' . app('tenant')->url . '/event/' . $this->event->identifier,
|
||||||
'location' => $this->event->location,
|
'location' => $this->event->location,
|
||||||
'postalCode' => $this->event->postal_code,
|
'postalCode' => $this->event->postal_code,
|
||||||
'email' => $this->event->email,
|
'email' => $this->event->email,
|
||||||
'accountOwner' => $this->event->account_owner,
|
'accountOwner' => $this->event->account_owner,
|
||||||
'accountIban' => $this->event->account_iban,
|
'accountIban' => $this->event->account_iban,
|
||||||
'accountOwner' => $this->event->account_owner,
|
|
||||||
'accountIban' => $this->event->account_iban,
|
|
||||||
'alcoholicsAge' => $this->event->alcoholics_age,
|
'alcoholicsAge' => $this->event->alcoholics_age,
|
||||||
'sendWeeklyReports' => $this->event->send_weekly_report,
|
'sendWeeklyReports' => $this->event->send_weekly_report,
|
||||||
'registrationAllowed' => $this->event->registration_allowed,
|
'registrationAllowed' => $this->event->registration_allowed,
|
||||||
|
'archived' => $this->event->archived,
|
||||||
'earlyBirdEnd' => ['internal' => $this->event->early_bird_end->format('Y-m-d'), 'formatted' => $this->event->early_bird_end->format('d.m.Y')],
|
'earlyBirdEnd' => ['internal' => $this->event->early_bird_end->format('Y-m-d'), 'formatted' => $this->event->early_bird_end->format('d.m.Y')],
|
||||||
'registrationFinalEnd' => ['internal' => $this->event->registration_final_end->format('Y-m-d'), 'formatted' => $this->event->registration_final_end->format('d.m.Y')],
|
'registrationFinalEnd' => ['internal' => $this->event->registration_final_end->format('Y-m-d'), 'formatted' => $this->event->registration_final_end->format('d.m.Y')],
|
||||||
'refundAfterEarlyBirdEnd' => 100 - $this->event->early_bird_end_amount_increase,
|
'refundAfterEarlyBirdEnd' => 100 - $this->event->early_bird_end_amount_increase,
|
||||||
@@ -86,6 +86,7 @@ class EventResource extends JsonResource{
|
|||||||
$returnArray['eventEnd'] = $this->event->end_date->format('d.m.Y');
|
$returnArray['eventEnd'] = $this->event->end_date->format('d.m.Y');
|
||||||
$returnArray['eventEndInternal'] = $this->event->end_date;
|
$returnArray['eventEndInternal'] = $this->event->end_date;
|
||||||
$returnArray['duration'] = $duration;
|
$returnArray['duration'] = $duration;
|
||||||
|
$returnArray['totalParticipantCount'] = $this->event->participants()->count();
|
||||||
|
|
||||||
$returnArray['supportPersonIndex'] = $this->event->support_per_person->toString();
|
$returnArray['supportPersonIndex'] = $this->event->support_per_person->toString();
|
||||||
$returnArray['supportPerson'] = $this->calculateSupportPerPerson($returnArray['participants']);
|
$returnArray['supportPerson'] = $this->calculateSupportPerPerson($returnArray['participants']);
|
||||||
@@ -95,12 +96,15 @@ class EventResource extends JsonResource{
|
|||||||
|
|
||||||
$totalBalanceReal = new Amount(0, 'Euro');
|
$totalBalanceReal = new Amount(0, 'Euro');
|
||||||
$totalBalanceExpected = new Amount(0, 'Euro');
|
$totalBalanceExpected = new Amount(0, 'Euro');
|
||||||
|
$totalBalanceEstimated = new Amount(0, 'Euro');
|
||||||
|
|
||||||
$totalBalanceReal->addAmount($returnArray['income']['real']['amount']);
|
$totalBalanceReal->addAmount($returnArray['income']['real']['amount']);
|
||||||
$totalBalanceExpected->addAmount($returnArray['income']['expected']['amount']);
|
$totalBalanceExpected->addAmount($returnArray['income']['expected']['amount']);
|
||||||
|
$totalBalanceEstimated->addAmount($returnArray['income']['expected']['amount']);
|
||||||
|
|
||||||
$totalBalanceReal->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
$totalBalanceReal->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
||||||
$totalBalanceExpected->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
$totalBalanceExpected->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
||||||
|
$totalBalanceEstimated->subtractAmount($returnArray['costUnit']['overAllEstimatedAmount']['value']);
|
||||||
$returnArray['totalBalance'] = [
|
$returnArray['totalBalance'] = [
|
||||||
'real' => [
|
'real' => [
|
||||||
'value' => $totalBalanceReal->getAmount(),
|
'value' => $totalBalanceReal->getAmount(),
|
||||||
@@ -108,6 +112,10 @@ class EventResource extends JsonResource{
|
|||||||
], 'expected' => [
|
], 'expected' => [
|
||||||
'value' => $totalBalanceExpected->getAmount(),
|
'value' => $totalBalanceExpected->getAmount(),
|
||||||
'readable' => $totalBalanceExpected->toString(),
|
'readable' => $totalBalanceExpected->toString(),
|
||||||
|
],
|
||||||
|
'estimated' => [
|
||||||
|
'value' => $totalBalanceEstimated->getAmount(),
|
||||||
|
'readable' => $totalBalanceEstimated->toString(),
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ class InvoiceResource {
|
|||||||
$returnData['amount'] = Amount::fromString($this->invoice->amount, ' Euro')->toString();
|
$returnData['amount'] = Amount::fromString($this->invoice->amount, ' Euro')->toString();
|
||||||
$returnData['id'] = $this->invoice->id;
|
$returnData['id'] = $this->invoice->id;
|
||||||
$returnData['donation'] = $this->invoice->donation;
|
$returnData['donation'] = $this->invoice->donation;
|
||||||
$returnData['alreadyPaid'] = !$this->invoice->donation && null === $this->invoice->contact_bank_iban;
|
$returnData['externalPayment'] = null !== $this->invoice->payment_purpose;
|
||||||
|
$returnData['paymentPurpose'] = $this->invoice->payment_purpose ?? 'Auslagenerstattung Rechnungsnummer ' . $returnData['invoiceNumber'];
|
||||||
$returnData['accountOwner'] = $this->invoice->contact_bank_owner ?? '--';
|
$returnData['accountOwner'] = $this->invoice->contact_bank_owner ?? '--';
|
||||||
$returnData['accountIban'] = $this->invoice->contact_bank_iban ?? '--';
|
$returnData['accountIban'] = $this->invoice->contact_bank_iban ?? '--';
|
||||||
$returnData['status'] = $this->invoice->status;
|
$returnData['status'] = $this->invoice->status;
|
||||||
@@ -67,7 +68,7 @@ class InvoiceResource {
|
|||||||
$returnData['approvedBy'] = $this->invoice->approvedBy()->first()->username ?? '--';
|
$returnData['approvedBy'] = $this->invoice->approvedBy()->first()->username ?? '--';
|
||||||
}
|
}
|
||||||
|
|
||||||
$returnData['alreadyPaid'] = $returnData['alreadyPaid'] == '' ? 0 : 1;
|
$returnData['externalPayment'] = $returnData['externalPayment'] == '' ? 0 : 1;
|
||||||
|
|
||||||
return $returnData;
|
return $returnData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Scopes;
|
|||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Providers\AuthCheckProvider;
|
use App\Providers\AuthCheckProvider;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
|
use App\Repositories\EstimatesRepository;
|
||||||
use App\Repositories\EventParticipantRepository;
|
use App\Repositories\EventParticipantRepository;
|
||||||
use App\Repositories\EventRepository;
|
use App\Repositories\EventRepository;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
@@ -21,6 +22,7 @@ abstract class CommonController {
|
|||||||
protected InvoiceRepository $invoices;
|
protected InvoiceRepository $invoices;
|
||||||
protected EventRepository $events;
|
protected EventRepository $events;
|
||||||
protected EventParticipantRepository $eventParticipants;
|
protected EventParticipantRepository $eventParticipants;
|
||||||
|
protected EstimatesRepository $estimates;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->tenant = app('tenant');
|
$this->tenant = app('tenant');
|
||||||
@@ -30,6 +32,7 @@ abstract class CommonController {
|
|||||||
$this->invoices = new InvoiceRepository();
|
$this->invoices = new InvoiceRepository();
|
||||||
$this->events = new EventRepository();
|
$this->events = new EventRepository();
|
||||||
$this->eventParticipants = new EventParticipantRepository();
|
$this->eventParticipants = new EventParticipantRepository();
|
||||||
|
$this->estimates = new EstimatesRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function checkAuth() {
|
protected function checkAuth() {
|
||||||
|
|||||||
@@ -2,19 +2,48 @@
|
|||||||
|
|
||||||
namespace App\Tasks;
|
namespace App\Tasks;
|
||||||
|
|
||||||
|
use App\Domains\CostUnit\Actions\ChangeCostUnitDetails\ChangeCostUnitDetailsCommand;
|
||||||
|
use App\Domains\CostUnit\Actions\ChangeCostUnitDetails\ChangeCostUnitDetailsRequest;
|
||||||
|
use App\Domains\CostUnit\Actions\ChangeCostUnitState\ChangeCostUnitStateCommand;
|
||||||
|
use App\Domains\CostUnit\Actions\ChangeCostUnitState\ChangeCostUnitStateRequest;
|
||||||
use App\Models\CostUnit;
|
use App\Models\CostUnit;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
class CloseCostUnit implements CronTask {
|
class CloseCostUnit implements CronTask {
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$now = now();
|
$now = now();
|
||||||
$costUnitRepository = new CostUnitRepository();
|
$costUnitRepository = new CostUnitRepository();
|
||||||
|
$now = now();
|
||||||
|
$billingResetEnd = clone $now;
|
||||||
|
$billingResetEnd->add(new \DateInterval('P6W'));
|
||||||
/** @var CostUnit $costUnit */
|
/** @var CostUnit $costUnit */
|
||||||
foreach ($costUnitRepository->getCurrentEvents() as $costUnit) {
|
foreach ($costUnitRepository->getCurrentEvents(true) as $costUnit) {
|
||||||
if (\DateTime::createFromFormat('Y-m-d', $costUnit->billing_deadline) < $now ) {
|
$billingEnd = $costUnit['billing_deadline'];
|
||||||
$costUnit->allow_new = false;
|
|
||||||
$costUnit->save();
|
if ($billingEnd === null) {
|
||||||
|
$billingEnd = \DateTime::createFromFormat('Y-m-d H:i:s', $billingResetEnd)->format('Y-m-d H:i:s');
|
||||||
|
new ChangeCostUnitDetailsCommand(new ChangeCostUnitDetailsRequest(
|
||||||
|
CostUnit::where('id', $costUnit['id'])->first(),
|
||||||
|
Amount::fromString($costUnit['distance_allowance']),
|
||||||
|
$costUnit['mail_on_new'],
|
||||||
|
$billingResetEnd
|
||||||
|
))->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
$billingEndTime = \DateTime::createFromFormat('Y-m-d H:i:s', $billingEnd);
|
||||||
|
$billingEndTime->setTime(0,0,0);
|
||||||
|
$billingEndTime->add(new \DateInterval('P1D'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ($billingEndTime < $now) {
|
||||||
|
new ChangeCostUnitStateCommand(
|
||||||
|
new ChangeCostUnitStateRequest(
|
||||||
|
CostUnit::where('id', $costUnit['id'])->first(),false, false
|
||||||
|
)
|
||||||
|
)->execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class CloseEvent implements CronTask {
|
|||||||
$eventRepository = new EventRepository();
|
$eventRepository = new EventRepository();
|
||||||
/** @var Event $event */
|
/** @var Event $event */
|
||||||
foreach ($eventRepository->getAvailable(false) as $event) {
|
foreach ($eventRepository->getAvailable(false) as $event) {
|
||||||
if ($event->registration_final_end < $now ) {
|
if ($event->registration_final_end <= $now ) {
|
||||||
$event->registration_allowed = false;
|
$event->registration_allowed = false;
|
||||||
$event->save();
|
$event->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ use Psr\Log\LoggerInterface;
|
|||||||
class NotifyTeam implements CronTask {
|
class NotifyTeam implements CronTask {
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
if (date('w') !== 0) {
|
if ((int)date('w') !== 0) {
|
||||||
// return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$eventRepository = new EventRepository();
|
$eventRepository = new EventRepository();
|
||||||
/** @var Event $event */
|
/** @var Event $event */
|
||||||
foreach ($eventRepository->getAvailable(false) as $event) {
|
foreach ($eventRepository->getAvailable(false) as $event) {
|
||||||
foreach (Tenant::all() as $tenant) {
|
foreach (Tenant::all() as $tenant) {
|
||||||
|
echo $tenant->slug . PHP_EOL;
|
||||||
$participants = $event->participants()->where('local_group', $tenant->slug)->whereNull('unregistered_at')->get();
|
$participants = $event->participants()->where('local_group', $tenant->slug)->whereNull('unregistered_at')->get();
|
||||||
}
|
|
||||||
|
|
||||||
if ($participants->isEmpty()) {
|
if ($participants->isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -38,4 +39,5 @@ class NotifyTeam implements CronTask {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!-- NumericInput.vue -->
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
number: { type: String, required: true },
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a class="link" :href="'tel:' + props.number">{{ props.number }}</a>
|
||||||
|
</template>
|
||||||
@@ -126,6 +126,14 @@ onUnmounted(() => {
|
|||||||
right: 15px;
|
right: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
|
z-index: 10;
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animation */
|
/* Animation */
|
||||||
@@ -146,4 +154,41 @@ onUnmounted(() => {
|
|||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.full-screen-modal-content {
|
||||||
|
top: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-screen-modal-body {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.full-screen-modal-content {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-screen-modal-body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-screen-modal-close {
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
font-size: 26px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -47,15 +47,11 @@ function close() {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. ESC-Key schließt Modal
|
|
||||||
*/
|
|
||||||
function handleKeyDown(e) {
|
function handleKeyDown(e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Focus-Trap
|
|
||||||
if (e.key === 'Tab' && modalRef.value) {
|
if (e.key === 'Tab' && modalRef.value) {
|
||||||
const focusable = modalRef.value.querySelectorAll(
|
const focusable = modalRef.value.querySelectorAll(
|
||||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||||
@@ -75,16 +71,13 @@ function handleKeyDown(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 3. Body-Scroll sperren wenn Modal offen
|
|
||||||
*/
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
async (newVal) => {
|
async (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
document.body.style.overflow = 'hidden'
|
document.body.style.overflow = 'hidden'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
modalRef.value?.focus() // Setzt Focus ins Modal
|
modalRef.value?.focus()
|
||||||
} else {
|
} else {
|
||||||
document.body.style.overflow = ''
|
document.body.style.overflow = ''
|
||||||
}
|
}
|
||||||
@@ -96,7 +89,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('keydown', handleKeyDown)
|
window.removeEventListener('keydown', handleKeyDown)
|
||||||
document.body.style.overflow = '' // fallback cleanup
|
document.body.style.overflow = ''
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -109,6 +102,7 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: white;
|
background: white;
|
||||||
@@ -120,6 +114,11 @@ onUnmounted(() => {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Animation */
|
/* Animation */
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
.fade-leave-active {
|
.fade-leave-active {
|
||||||
@@ -138,4 +137,34 @@ onUnmounted(() => {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.modal-content {
|
||||||
|
width: 95% !important;
|
||||||
|
max-width: 95vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.modal-overlay {
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 75vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const props = defineProps({
|
|||||||
tabs: {
|
tabs: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
// [{ title: "Titel", component: Component, endpoint: "/wp-json/..." }]
|
|
||||||
},
|
},
|
||||||
subTabIndex: {
|
subTabIndex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -14,9 +13,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const activeTab = ref(null) // aktuell ausgewählter Tab
|
const activeTab = ref(null)
|
||||||
const tabData = ref({}) // Daten für jeden Tab
|
const tabData = ref({})
|
||||||
const tabComponent = shallowRef(null) // Komponente für aktuellen Tab
|
const tabComponent = shallowRef(null)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref(null)
|
const error = ref(null)
|
||||||
|
|
||||||
@@ -34,13 +33,13 @@ async function selectTab(index) {
|
|||||||
?.getAttribute('content')
|
?.getAttribute('content')
|
||||||
|
|
||||||
const response = await fetch(tab.endpoint, {
|
const response = await fetch(tab.endpoint, {
|
||||||
method: 'GET', // oder POST, PUT, DELETE …
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'X-CSRF-TOKEN': csrfToken,
|
'X-CSRF-TOKEN': csrfToken,
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
},
|
},
|
||||||
credentials: 'same-origin', // wichtig für Session/Auth
|
credentials: 'same-origin',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Fehler: " + response.status)
|
if (!response.ok) throw new Error("Fehler: " + response.status)
|
||||||
@@ -56,7 +55,6 @@ async function selectTab(index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ersten Tab automatisch laden
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.tabs.length > 0) {
|
if (props.tabs.length > 0) {
|
||||||
selectTab(props.subTabIndex)
|
selectTab(props.subTabIndex)
|
||||||
@@ -66,7 +64,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<!-- Tab Header -->
|
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<button
|
<button
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
@@ -78,7 +75,6 @@ onMounted(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab Content -->
|
|
||||||
<div class="tab-content" id="tab-content">
|
<div class="tab-content" id="tab-content">
|
||||||
<div v-if="loading">⏳ Lädt...</div>
|
<div v-if="loading">⏳ Lädt...</div>
|
||||||
<div v-else-if="error">❌ {{ error }}</div>
|
<div v-else-if="error">❌ {{ error }}</div>
|
||||||
@@ -101,12 +97,17 @@ onMounted(() => {
|
|||||||
.tab-header {
|
.tab-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2px;
|
||||||
|
background: #f8f8f8;
|
||||||
}
|
}
|
||||||
.tab-button {
|
.tab-button {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
.tab-button.active {
|
.tab-button.active {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -116,5 +117,40 @@ onMounted(() => {
|
|||||||
.tab-content {
|
.tab-content {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Tablet ─── */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.tab-button {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Smartphone ─── */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.tab-header {
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button.active {
|
||||||
|
border-bottom: none;
|
||||||
|
border-left: 4px solid #0073aa;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<label :for="props.belongsTo">{{contentData.content}}</label>
|
<label :for="props.belongsTo" v-html="contentData.content"></label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -46,4 +46,12 @@ import UpcomingEvents from "../../../Domains/Dashboard/Views/Partials/Widgets/Up
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.widget-container {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -123,4 +123,5 @@ return [
|
|||||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'version' => trim(file_get_contents(base_path('version'))),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ return new class extends Migration {
|
|||||||
Schema::create('page_texts', function (Blueprint $table) {
|
Schema::create('page_texts', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('content');
|
$table->longText('content');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ return new class extends Migration {
|
|||||||
$table->string('name')->unique();
|
$table->string('name')->unique();
|
||||||
$table->string('execution_type');
|
$table->string('execution_type');
|
||||||
$table->time('schedule_time')->nullable();
|
$table->time('schedule_time')->nullable();
|
||||||
$table->timestamp('last_run')->nullable();
|
$table->json('last_run')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
$table->foreign('execution_type')->references('slug')->on('cron_task_types')->cascadeOnDelete()->cascadeOnUpdate();
|
$table->foreign('execution_type')->references('slug')->on('cron_task_types')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user