Compare commits
10 Commits
fd403f8520
...
dev-001
| Author | SHA1 | Date | |
|---|---|---|---|
| b1c333648a | |||
| fcf41c5d13 | |||
| 2b458eccd7 | |||
| 4f4dff2edd | |||
| cd526231ed | |||
| fa886aad4d | |||
| f468814a2f | |||
| ab711109a7 | |||
| 72623df38f | |||
| 9fd6839878 |
34
app/Casts/AmountCast.php
Normal file
34
app/Casts/AmountCast.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Casts;
|
||||||
|
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class AmountCast implements CastsAttributes
|
||||||
|
{
|
||||||
|
public function get(Model $model, string $key, mixed $value, array $attributes): ?Amount
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Amount((float) $value, 'Euro');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(Model $model, string $key, mixed $value, array $attributes): ?float
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof Amount) {
|
||||||
|
return $value->getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,8 +22,13 @@ class CreateCostUnitCommand {
|
|||||||
'mail_on_new' => $this->request->mailOnNew,
|
'mail_on_new' => $this->request->mailOnNew,
|
||||||
'allow_new' => true,
|
'allow_new' => true,
|
||||||
'archived' => false,
|
'archived' => false,
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (null !== $costUnit) {
|
||||||
|
$response->costUnit = $costUnit;
|
||||||
|
$response->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||||
|
|
||||||
class CreateCostUnitResponse {
|
use App\Models\CostUnit;
|
||||||
|
|
||||||
|
class CreateCostUnitResponse {
|
||||||
|
public bool $success;
|
||||||
|
public ?CostUnit $costUnit;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
$this->costUnit = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\CostUnit\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||||
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||||
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Providers\FileWriteProvider;
|
||||||
|
use App\Providers\InvoiceCsvFileProvider;
|
||||||
|
use App\Providers\PainFileProvider;
|
||||||
|
use App\Providers\WebDavProvider;
|
||||||
|
use App\Providers\ZipArchiveFileProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ExportController extends CommonController {
|
||||||
|
public function __invoke(int $costUnitId) {
|
||||||
|
$costUnit = $this->costUnits->getById($costUnitId);
|
||||||
|
|
||||||
|
$invoicesForExport = $this->invoices->getByStatus($costUnit, InvoiceStatus::INVOICE_STATUS_APPROVED, false);
|
||||||
|
|
||||||
|
$webdavProvider = new WebDavProvider(WebDavProvider::INVOICE_PREFIX . $this->tenant->url . '/' . $costUnit->name);
|
||||||
|
|
||||||
|
$painFileData = $this->painData($invoicesForExport);
|
||||||
|
$csvData = $this->csvData($invoicesForExport);
|
||||||
|
|
||||||
|
$filePrefix = Tenant::getTempDirectory();
|
||||||
|
|
||||||
|
$painFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '-sepa.xml', $painFileData);
|
||||||
|
$painFileWriteProvider->writeToFile();
|
||||||
|
|
||||||
|
$csvFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '.csv', $csvData);
|
||||||
|
$csvFileWriteProvider->writeToFile();
|
||||||
|
|
||||||
|
if ($this->tenant->upload_exports) {
|
||||||
|
$webdavProvider->uploadFile($painFileWriteProvider->fileName);
|
||||||
|
$webdavProvider->uploadFile($csvFileWriteProvider->fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadZipArchiveFiles = [
|
||||||
|
$painFileWriteProvider->fileName,
|
||||||
|
$csvFileWriteProvider->fileName
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($invoicesForExport as $invoice) {
|
||||||
|
$changeStatusRequest = new ChangeStatusRequest($invoice, InvoiceStatus::INVOICE_STATUS_EXPORTED);
|
||||||
|
$changeStatusCommand = new ChangeStatusCommand($changeStatusRequest);
|
||||||
|
$changeStatusCommand->execute();
|
||||||
|
|
||||||
|
if ($this->tenant->download_exports) {
|
||||||
|
$createInvoiceReceiptRequest = new CreateInvoiceReceiptRequest($invoice);
|
||||||
|
$createInvoiceReceiptCommand = new CreateInvoiceReceiptCommand($createInvoiceReceiptRequest);
|
||||||
|
$response = $createInvoiceReceiptCommand->execute();
|
||||||
|
|
||||||
|
$downloadZipArchiveFiles[] = $response->fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->tenant->download_exports) {
|
||||||
|
$zipFile = Tenant::getTempDirectory() . 'Abrechnungen-' . $costUnit->name . '.zip';
|
||||||
|
$zipFileProvider = new ZipArchiveFileProvider($zipFile);
|
||||||
|
foreach ($downloadZipArchiveFiles as $file) {
|
||||||
|
$zipFileProvider->addFile($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zipFileProvider->create();
|
||||||
|
foreach ($downloadZipArchiveFiles as $file) {
|
||||||
|
Storage::delete($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::delete($painFileWriteProvider->fileName);
|
||||||
|
Storage::delete($csvFileWriteProvider->fileName);
|
||||||
|
|
||||||
|
return response()->download(
|
||||||
|
storage_path('app/private/' . $zipFile),
|
||||||
|
basename($zipFile),
|
||||||
|
['Content-Type' => 'application/zip']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::delete($painFileWriteProvider->fileName);
|
||||||
|
Storage::delete($csvFileWriteProvider->fileName);
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Die Abrechnungen wurden exportiert.' . PHP_EOL .'Die Belege werden asynchron auf dem Webdav-Server hinterlegt.' . PHP_EOL . PHP_EOL . 'Sollten diese in 15 Minuten nicht vollständig sein, kontaktiere den Adminbistrator.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function painData(array $invoices) : string {
|
||||||
|
$invoicesForPainFile = [];
|
||||||
|
foreach ($invoices as $invoice) {
|
||||||
|
if ($invoice->contact_bank_owner !== null && $invoice->contact_bank_iban !== '' && !$invoice->donation) {
|
||||||
|
$invoicesForPainFile[] = $invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$painFileProvider = new PainFileProvider($this->tenant->account_iban, $this->tenant->account_name, $this->tenant->account_bic, $invoicesForPainFile);
|
||||||
|
return $painFileProvider->createPainFileContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function csvData(array $invoices) : string {
|
||||||
|
$csvDateProvider = new InvoiceCsvFileProvider($invoices);
|
||||||
|
return $csvDateProvider->createCsvFileContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,8 +8,11 @@ use Illuminate\Http\JsonResponse;
|
|||||||
|
|
||||||
class OpenController extends CommonController {
|
class OpenController extends CommonController {
|
||||||
public function __invoke(int $costUnitId) {
|
public function __invoke(int $costUnitId) {
|
||||||
|
$costUnit = $this->costUnits->getById($costUnitId);
|
||||||
|
|
||||||
|
|
||||||
$inertiaProvider = new InertiaProvider('CostUnit/Open', [
|
$inertiaProvider = new InertiaProvider('CostUnit/Open', [
|
||||||
'costUnitId' => $costUnitId
|
'costUnit' => $costUnit
|
||||||
]);
|
]);
|
||||||
return $inertiaProvider->render();
|
return $inertiaProvider->render();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use App\Domains\CostUnit\Controllers\ChangeStateController;
|
|||||||
use App\Domains\CostUnit\Controllers\CreateController;
|
use App\Domains\CostUnit\Controllers\CreateController;
|
||||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||||
use App\Domains\CostUnit\Controllers\EditController;
|
use App\Domains\CostUnit\Controllers\EditController;
|
||||||
|
use App\Domains\CostUnit\Controllers\ExportController;
|
||||||
use App\Domains\CostUnit\Controllers\ListController;
|
use App\Domains\CostUnit\Controllers\ListController;
|
||||||
use App\Domains\CostUnit\Controllers\OpenController;
|
use App\Domains\CostUnit\Controllers\OpenController;
|
||||||
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
||||||
@@ -37,6 +38,7 @@ Route::prefix('api/v1')
|
|||||||
Route::get('/treasurers', TreasurersEditController::class);
|
Route::get('/treasurers', TreasurersEditController::class);
|
||||||
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
||||||
|
|
||||||
|
Route::get('/export-payouts', ExportController::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
import ListInvoices from "./Partials/ListInvoices.vue";
|
import ListInvoices from "./Partials/ListInvoices.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
costUnitId: Number
|
costUnit: Object
|
||||||
})
|
})
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
const initialCostUnitId = props.cost_unit_id
|
const initialCostUnitId = props.costUnit.id
|
||||||
const initialInvoiceId = props.invoice_id
|
const initialInvoiceId = props.invoice_id
|
||||||
|
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
title: 'Neue Abrechnungen',
|
title: 'Neue Abrechnungen',
|
||||||
component: ListInvoices,
|
component: ListInvoices,
|
||||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/new",
|
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/new",
|
||||||
deep_jump_id: initialCostUnitId,
|
deep_jump_id: initialCostUnitId,
|
||||||
deep_jump_id_sub: initialInvoiceId,
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
|
|
||||||
@@ -30,21 +30,21 @@
|
|||||||
{
|
{
|
||||||
title: 'Nichtexportierte Abrechnungen',
|
title: 'Nichtexportierte Abrechnungen',
|
||||||
component: ListInvoices,
|
component: ListInvoices,
|
||||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/approved",
|
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/approved",
|
||||||
deep_jump_id: initialCostUnitId,
|
deep_jump_id: initialCostUnitId,
|
||||||
deep_jump_id_sub: initialInvoiceId,
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Exportierte Abrechnungen',
|
title: 'Exportierte Abrechnungen',
|
||||||
component: ListInvoices,
|
component: ListInvoices,
|
||||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/exported",
|
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/exported",
|
||||||
deep_jump_id: initialCostUnitId,
|
deep_jump_id: initialCostUnitId,
|
||||||
deep_jump_id_sub: initialInvoiceId,
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Abgelehnte Abrechnungen',
|
title: 'Abgelehnte Abrechnungen',
|
||||||
component: ListInvoices,
|
component: ListInvoices,
|
||||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/denied",
|
endpoint: "/api/v1/cost-unit/" + props.costUnit.id + "/invoice-list/denied",
|
||||||
deep_jump_id: initialCostUnitId,
|
deep_jump_id: initialCostUnitId,
|
||||||
deep_jump_id_sub: initialInvoiceId,
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
},
|
},
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppLayout title="Abrechnungen">
|
<AppLayout :title="'Abrechnungen ' + props.costUnit.name">
|
||||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" :initial-sub-tab-id="initialInvoiceId" />
|
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" :initial-sub-tab-id="initialInvoiceId" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {createApp, ref} from 'vue'
|
import {createApp, ref} from 'vue'
|
||||||
/*import {
|
import LoadingModal from "../../../../Views/Components/LoadingModal.vue";
|
||||||
_mareike_download_as_zip,
|
|
||||||
|
|
||||||
_mareike_use_webdav
|
|
||||||
} from "../../../assets/javascripts/library";*/
|
|
||||||
//import LoadingModal from "../../../assets/components/LoadingModal.vue";
|
|
||||||
//import Invoices from '../invoices/index.vue'
|
|
||||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
import CostUnitDetails from "./CostUnitDetails.vue";
|
import CostUnitDetails from "./CostUnitDetails.vue";
|
||||||
import {toast} from "vue3-toastify";
|
import {toast} from "vue3-toastify";
|
||||||
import Treasurers from "./Treasurers.vue";
|
import Treasurers from "./Treasurers.vue";
|
||||||
|
|
||||||
//import CostUnitDetails from "./CostUnitDetails.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: [Array, Object],
|
type: [Array, Object],
|
||||||
@@ -42,13 +34,8 @@ const show_cost_unit = ref(false)
|
|||||||
const showTreasurers = ref(false)
|
const showTreasurers = ref(false)
|
||||||
const costUnit = ref(null)
|
const costUnit = ref(null)
|
||||||
|
|
||||||
|
|
||||||
const { data, loading, error, request, download } = useAjax()
|
const { data, loading, error, request, download } = useAjax()
|
||||||
|
|
||||||
if (props.deep_jump_id > 0) {
|
|
||||||
// open_invoice_list(props.deep_jump_id, 'new', props.deep_jump_id_sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function costUnitDetails(costUnitId) {
|
async function costUnitDetails(costUnitId) {
|
||||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -114,41 +101,46 @@ async function changeCostUnitState(costUnitId, endPoint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function export_payouts(cost_unit_id) {
|
async function exportPayouts(costUnitId) {
|
||||||
showLoading.value = true;
|
showLoading.value = true;
|
||||||
|
|
||||||
|
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||||
|
const data = await response.json();
|
||||||
|
const exportUrl = '/api/v1/cost-unit/' + costUnitId + '/export-payouts';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (_mareike_download_as_zip()) {
|
if (data.tenant.download_exports) {
|
||||||
const response = await fetch("/wp-json/mareike/costunits/export-payouts", {
|
const response = await fetch(exportUrl, {
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
|
||||||
mareike_nonce: _mareike_nonce(),
|
|
||||||
costunit: cost_unit_id,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
||||||
|
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const downloadUrl = window.URL.createObjectURL(blob);
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
console.log(response.headers.get("content-type"));
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
a.href = downloadUrl;
|
a.href = downloadUrl;
|
||||||
a.download = `payouts-${cost_unit_id}.zip`;
|
a.download = "Abrechnungen-Sippenstunden.zip";
|
||||||
|
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
|
||||||
|
setTimeout(() => {
|
||||||
window.URL.revokeObjectURL(downloadUrl);
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
await request("/wp-json/mareike/costunits/export-payouts", {
|
const response = await request(exportUrl, {
|
||||||
method: "POST",
|
method: "GET",
|
||||||
body: {
|
|
||||||
mareike_nonce: _mareike_nonce(),
|
|
||||||
costunit: cost_unit_id,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
toast.success(response.message);
|
||||||
}
|
}
|
||||||
showLoading.value = false;
|
showLoading.value = false;
|
||||||
toast.success('Die Abrechnungen wurden exportiert.');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showLoading.value = false;
|
showLoading.value = false;
|
||||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||||
@@ -176,7 +168,7 @@ async function export_payouts(cost_unit_id) {
|
|||||||
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
||||||
<input v-else type="button" value="Abrechnungen einsehen" />
|
<input v-else type="button" value="Abrechnungen einsehen" />
|
||||||
<br />
|
<br />
|
||||||
<input v-if="!costUnit.archived" type="button" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
<input v-if="!costUnit.archived" type="button" @click="exportPayouts(costUnit.id)" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@@ -217,17 +209,10 @@ async function export_payouts(cost_unit_id) {
|
|||||||
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div v-else-if="showInvoiceList">
|
<div v-else-if="showInvoiceList">
|
||||||
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
||||||
|
|
||||||
|
|
||||||
<LoadingModal :show="showLoading" v-if="_mareike_use_webdav()" message="Die PDF-Dateien werden asynchron erzeugt, diese sollten in 10 Minuten auf dem Webdav-Server liegen', 'mareike')" />
|
|
||||||
<LoadingModal :show="showLoading" v-else message='Die Abrechnungen werden exportiert, bitte warten.' />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@@ -236,7 +221,7 @@ async function export_payouts(cost_unit_id) {
|
|||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LoadingModal :show="showLoading" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const commonProps = reactive({
|
|||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const response = await fetch('/api/v1/retreive-global-data');
|
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
Object.assign(commonProps, data);
|
Object.assign(commonProps, data);
|
||||||
|
|
||||||
@@ -74,8 +74,7 @@ console.log(props.data.treasurers)
|
|||||||
title="Schatzis zuweisen"
|
title="Schatzis zuweisen"
|
||||||
@close="emit('closeTreasurers')"
|
@close="emit('closeTreasurers')"
|
||||||
>
|
>
|
||||||
Zuständige Schatzis:
|
<h3>Zuständige Schatzis:</h3>
|
||||||
|
|
||||||
<p v-for="user in commonProps.activeUsers">
|
<p v-for="user in commonProps.activeUsers">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ class DashboardController extends CommonController {
|
|||||||
|
|
||||||
private function renderForLoggedInUser(Request $request) {
|
private function renderForLoggedInUser(Request $request) {
|
||||||
$authCheckProvider = new AuthCheckProvider;
|
$authCheckProvider = new AuthCheckProvider;
|
||||||
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', ['appName' => app('tenant')->name]);
|
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', [
|
||||||
|
'myInvoices' => $this->invoices->getMyInvoicesWidget()
|
||||||
|
|
||||||
|
]);
|
||||||
return $inertiaProvider->render();
|
return $inertiaProvider->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
import {onMounted} from "vue";
|
import MyInvoices from "./Partials/Widgets/MyInvoices.vue";
|
||||||
import {toast} from "vue3-toastify";
|
|
||||||
|
const props = defineProps({
|
||||||
|
myInvoices: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function newInvoice() {
|
||||||
|
window.location.href = '/invoice/new';
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -13,7 +22,10 @@ import {toast} from "vue3-toastify";
|
|||||||
</shadowed-box>
|
</shadowed-box>
|
||||||
|
|
||||||
<shadowed-box class="dashboard-widget-box">
|
<shadowed-box class="dashboard-widget-box">
|
||||||
Meine Abrechnungen
|
<MyInvoices />
|
||||||
|
<input type="button" value="Neue Abrechnung" @click="newInvoice" style="margin-top: 20px;">
|
||||||
|
|
||||||
|
|
||||||
</shadowed-box>
|
</shadowed-box>
|
||||||
</diV>
|
</diV>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
@@ -31,7 +43,6 @@ import {toast} from "vue3-toastify";
|
|||||||
|
|
||||||
.dashboard-widget-box {
|
.dashboard-widget-box {
|
||||||
flex-grow: 1; display: inline-block;
|
flex-grow: 1; display: inline-block;
|
||||||
height: 150px;
|
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<p v-for="invoice in myInvoices.myInvoices" class="widget-content-item">
|
<p v-for="invoice in myInvoices.myInvoices" class="widget-content-item">
|
||||||
<a :href="'/invoices/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
<a :href="'/invoice/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
||||||
<label>
|
<label>
|
||||||
{{invoice.amount}}
|
{{invoice.amount}}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
74
app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
Normal file
74
app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\CreateEvent;
|
||||||
|
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\RelationModels\EventEatingHabits;
|
||||||
|
use App\RelationModels\EventLocalGroups;
|
||||||
|
|
||||||
|
class CreateEventCommand {
|
||||||
|
private CreateEventRequest $request;
|
||||||
|
public function __construct(CreateEventRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
public function execute(): CreateEventResponse {
|
||||||
|
$response = new CreateEventResponse();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$prefix = $this->request->begin->format('Y-m_');
|
||||||
|
if (!str_starts_with($this->request->name, $prefix)) {
|
||||||
|
$this->request->name = $prefix . $this->request->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$event = Event::create([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'name' => $this->request->name,
|
||||||
|
'location' => $this->request->location,
|
||||||
|
'postal_code' => $this->request->postalCode,
|
||||||
|
'email' => $this->request->email,
|
||||||
|
'start_date' => $this->request->begin,
|
||||||
|
'end_date' => $this->request->end,
|
||||||
|
'early_bird_end' => $this->request->earlyBirdEnd,
|
||||||
|
'registration_final_end' => $this->request->registrationFinalEnd,
|
||||||
|
'early_bird_end_amount_increase' => $this->request->earlyBirdEndAmountIncrease,
|
||||||
|
'account_owner' => $this->request->accountOwner,
|
||||||
|
'account_iban' => $this->request->accountIban,
|
||||||
|
'participation_fee_type' => $this->request->participationFeeType->slug,
|
||||||
|
'pay_per_day' => $this->request->payPerDay,
|
||||||
|
'pay_direct' => $this->request->payDirect,
|
||||||
|
'total_max_amount' => 0,
|
||||||
|
'support_per_person' => 0,
|
||||||
|
'support_flat' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($event !== null) {
|
||||||
|
EventEatingHabits::create([
|
||||||
|
'event_id' => $event->id,
|
||||||
|
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGAN)->first()->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
EventEatingHabits::create([
|
||||||
|
'event_id' => $event->id,
|
||||||
|
'eating_habit_id' => EatingHabit::where('slug', EatingHabit::EATING_HABIT_VEGETARIAN)->first()->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (app('tenant')->slug === 'lv') {
|
||||||
|
foreach(Tenant::where(['is_active_local_group' => true])->get() as $tenant) {
|
||||||
|
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => $tenant->id]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EventLocalGroups::create(['event_id' => $event->id, 'local_group_id' => app('tenant')->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->event = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Domains/Event/Actions/CreateEvent/CreateEventRequest.php
Normal file
40
app/Domains/Event/Actions/CreateEvent/CreateEventRequest.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\CreateEvent;
|
||||||
|
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class CreateEventRequest {
|
||||||
|
public string $name;
|
||||||
|
public string $location;
|
||||||
|
public string $postalCode;
|
||||||
|
public string $email;
|
||||||
|
public DateTime $begin;
|
||||||
|
public DateTime $end;
|
||||||
|
public DateTime $earlyBirdEnd;
|
||||||
|
public DateTime $registrationFinalEnd;
|
||||||
|
public int $earlyBirdEndAmountIncrease;
|
||||||
|
public ParticipationFeeType $participationFeeType;
|
||||||
|
public string $accountOwner;
|
||||||
|
public string $accountIban;
|
||||||
|
public bool $payPerDay;
|
||||||
|
public bool $payDirect;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $location, string $postalCode, string $email, DateTime $begin, DateTime $end, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $earlyBirdEndAmountIncrease, ParticipationFeeType $participationFeeType, string $accountOwner, string $accountIban, bool $payPerDay, bool $payDirect) {
|
||||||
|
$this->name = $name;
|
||||||
|
$this->location = $location;
|
||||||
|
$this->postalCode = $postalCode;
|
||||||
|
$this->email = $email;
|
||||||
|
$this->begin = $begin;
|
||||||
|
$this->end = $end;
|
||||||
|
$this->earlyBirdEnd = $earlyBirdEnd;
|
||||||
|
$this->registrationFinalEnd = $registrationFinalEnd;
|
||||||
|
$this->earlyBirdEndAmountIncrease = $earlyBirdEndAmountIncrease;
|
||||||
|
$this->participationFeeType = $participationFeeType;
|
||||||
|
$this->accountOwner = $accountOwner;
|
||||||
|
$this->accountIban = $accountIban;
|
||||||
|
$this->payPerDay = $payPerDay;
|
||||||
|
$this->payDirect = $payDirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\CreateEvent;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
|
||||||
|
class CreateEventResponse {
|
||||||
|
public bool $success;
|
||||||
|
public ?Event $event;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
$this->event = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Domains/Event/Actions/SetCostUnit/SetCostUnitCommand.php
Normal file
18
app/Domains/Event/Actions/SetCostUnit/SetCostUnitCommand.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||||
|
|
||||||
|
class SetCostUnitCommand {
|
||||||
|
private SetCostUnitRequest $request;
|
||||||
|
|
||||||
|
public function __construct(SetCostUnitRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : SetCostUnitResponse {
|
||||||
|
$response = new SetCostUnitResponse();
|
||||||
|
$this->request->event->cost_unit_id = $this->request->costUnit->id;
|
||||||
|
$response->success = $this->request->event->save();
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Domains/Event/Actions/SetCostUnit/SetCostUnitRequest.php
Normal file
16
app/Domains/Event/Actions/SetCostUnit/SetCostUnitRequest.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||||
|
|
||||||
|
use App\Models\CostUnit;
|
||||||
|
use App\Models\Event;
|
||||||
|
|
||||||
|
class SetCostUnitRequest {
|
||||||
|
public Event $event;
|
||||||
|
public CostUnit $costUnit;
|
||||||
|
|
||||||
|
public function __construct(Event $event, CostUnit $costUnit) {
|
||||||
|
$this->event = $event;
|
||||||
|
$this->costUnit = $costUnit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetCostUnit;
|
||||||
|
|
||||||
|
class SetCostUnitResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||||
|
|
||||||
|
use App\RelationModels\EventParticipationFee;
|
||||||
|
|
||||||
|
class SetParticipationFeesCommand {
|
||||||
|
private SetParticipationFeesRequest $request;
|
||||||
|
public function __construct(SetParticipationFeesRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function excetute() : SetParticipationFeesResponse {
|
||||||
|
$response = new SetParticipationFeesResponse();
|
||||||
|
$this->cleanBefore();
|
||||||
|
|
||||||
|
$this->request->event->participationFee1()->associate(EventParticipationFee::create([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'type' => $this->request->participationFeeFirst['type'],
|
||||||
|
'name' => $this->request->participationFeeFirst['name'],
|
||||||
|
'description' => $this->request->participationFeeFirst['description'],
|
||||||
|
'amount' => $this->request->participationFeeFirst['amount']->getAmount()
|
||||||
|
]))->save();
|
||||||
|
|
||||||
|
if ($this->request->participationFeeSecond !== null) {
|
||||||
|
$this->request->event->participationFee2()->associate(EventParticipationFee::create([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'type' => $this->request->participationFeeSecond['type'],
|
||||||
|
'name' => $this->request->participationFeeSecond['name'],
|
||||||
|
'description' => $this->request->participationFeeSecond['description'],
|
||||||
|
'amount' => $this->request->participationFeeSecond['amount']->getAmount()
|
||||||
|
]))->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->participationFeeThird !== null) {
|
||||||
|
$this->request->event->participationFee3()->associate(EventParticipationFee::create([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'type' => $this->request->participationFeeThird['type'],
|
||||||
|
'name' => $this->request->participationFeeThird['name'],
|
||||||
|
'description' => $this->request->participationFeeThird['description'],
|
||||||
|
'amount' => $this->request->participationFeeThird['amount']->getAmount()
|
||||||
|
]))->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->participationFeeFourth !== null) {
|
||||||
|
$this->request->event->participationFee4()->associate(EventParticipationFee::create([
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
'type' => $this->request->participationFeeFourth['type'],
|
||||||
|
'name' => $this->request->participationFeeFourth['name'],
|
||||||
|
'description' => $this->request->participationFeeFourth['description'],
|
||||||
|
'amount' => $this->request->participationFeeFourth['amount']->getAmount()
|
||||||
|
]))->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->event->save();
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanBefore() {
|
||||||
|
if ($this->request->event->participationFee1()->first() !== null) {
|
||||||
|
$this->request->event->participationFee1()->first()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->event->participationFee2()->first() !== null) {
|
||||||
|
$this->request->event->participationFee2()->first()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->event->participationFee3()->first() !== null) {
|
||||||
|
$this->request->event->participationFee3()->first()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->event->participationFee4()->first() !== null) {
|
||||||
|
$this->request->event->participationFee4()->first()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->event->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\RelationModels\EventParticipationFee;
|
||||||
|
|
||||||
|
class SetParticipationFeesRequest {
|
||||||
|
public Event $event;
|
||||||
|
public array $participationFeeFirst;
|
||||||
|
public ?array $participationFeeSecond;
|
||||||
|
public ?array $participationFeeThird;
|
||||||
|
public ?array $participationFeeFourth;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Event $event, array $participationFeeFirst) {
|
||||||
|
$this->event = $event;
|
||||||
|
$this->participationFeeFirst = $participationFeeFirst;
|
||||||
|
$this->participationFeeSecond = null;
|
||||||
|
$this->participationFeeThird = null;
|
||||||
|
$this->participationFeeFourth = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SetParticipationFees;
|
||||||
|
|
||||||
|
class SetParticipationFeesResponse {
|
||||||
|
public bool $success;
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php
Normal file
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||||
|
|
||||||
|
class UpdateEventCommand {
|
||||||
|
public UpdateEventRequest $request;
|
||||||
|
public function __construct(UpdateEventRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UpdateEventResponse {
|
||||||
|
$response = new UpdateEventResponse();
|
||||||
|
|
||||||
|
$this->request->event->name = $this->request->eventName;
|
||||||
|
$this->request->event->location = $this->request->eventLocation;
|
||||||
|
$this->request->event->postal_code = $this->request->postalCode;
|
||||||
|
$this->request->event->email = $this->request->email;
|
||||||
|
$this->request->event->early_bird_end = $this->request->earlyBirdEnd;
|
||||||
|
$this->request->event->registration_final_end = $this->request->registrationFinalEnd;
|
||||||
|
$this->request->event->alcoholics_age = $this->request->alcoholicsAge;
|
||||||
|
$this->request->event->support_per_person = $this->request->supportPerPerson;
|
||||||
|
$this->request->event->support_flat = $this->request->flatSupport;
|
||||||
|
$this->request->event->save();
|
||||||
|
|
||||||
|
$this->request->event->resetAllowedEatingHabits();
|
||||||
|
$this->request->event->resetContributingLocalGroups();
|
||||||
|
|
||||||
|
foreach($this->request->eatingHabits as $eatingHabit) {
|
||||||
|
$this->request->event->eatingHabits()->attach($eatingHabit);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($this->request->contributingLocalGroups as $contributingLocalGroup) {
|
||||||
|
$this->request->event->localGroups()->attach($contributingLocalGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->event->save();
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php
Normal file
41
app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class UpdateEventRequest {
|
||||||
|
public Event $event;
|
||||||
|
public string $eventName;
|
||||||
|
public string $eventLocation;
|
||||||
|
public string $postalCode;
|
||||||
|
public string $email;
|
||||||
|
public DateTime $earlyBirdEnd;
|
||||||
|
public DateTime $registrationFinalEnd;
|
||||||
|
public int $alcoholicsAge;
|
||||||
|
public bool $sendWeeklyReports;
|
||||||
|
public bool $registrationAllowed;
|
||||||
|
public Amount $flatSupport;
|
||||||
|
public Amount $supportPerPerson;
|
||||||
|
public array $contributingLocalGroups;
|
||||||
|
public array $eatingHabits;
|
||||||
|
|
||||||
|
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits) {
|
||||||
|
$this->event = $event;
|
||||||
|
$this->eventName = $eventName;
|
||||||
|
$this->eventLocation = $eventLocation;
|
||||||
|
$this->postalCode = $postalCode;
|
||||||
|
$this->email = $email;
|
||||||
|
$this->earlyBirdEnd = $earlyBirdEnd;
|
||||||
|
$this->registrationFinalEnd = $registrationFinalEnd;
|
||||||
|
$this->alcoholicsAge = $alcoholicsAge;
|
||||||
|
$this->sendWeeklyReports = $sendWeeklyReports;
|
||||||
|
$this->registrationAllowed = $registrationAllowed;
|
||||||
|
$this->flatSupport = $flatSupport;
|
||||||
|
$this->supportPerPerson = $supportPerPerson;
|
||||||
|
$this->contributingLocalGroups = $contributingLocalGroups;
|
||||||
|
$this->eatingHabits = $eatingHabits;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UpdateEvent;
|
||||||
|
|
||||||
|
class UpdateEventResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||||
|
|
||||||
|
class UpdateManagersCommand {
|
||||||
|
private UpdateManagersRequest $request;
|
||||||
|
|
||||||
|
public function __construct(UpdateManagersRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UpdateManagersResponse {
|
||||||
|
$response = new UpdateManagersResponse();
|
||||||
|
$this->request->event->resetMangers();
|
||||||
|
|
||||||
|
foreach ($this->request->managers as $manager) {
|
||||||
|
$this->request->event->eventManagers()->attach($manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->event->save();
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
|
||||||
|
class UpdateManagersRequest {
|
||||||
|
public Event $event;
|
||||||
|
public array $managers;
|
||||||
|
|
||||||
|
public function __construct(Event $event, array $managers) {
|
||||||
|
$this->managers = $managers;
|
||||||
|
$this->event = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\UpdateManagers;
|
||||||
|
|
||||||
|
class UpdateManagersResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
app/Domains/Event/Controllers/CreateController.php
Normal file
100
app/Domains/Event/Controllers/CreateController.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitCommand;
|
||||||
|
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||||
|
use App\Domains\Event\Actions\CreateEvent\CreateEventCommand;
|
||||||
|
use App\Domains\Event\Actions\CreateEvent\CreateEventRequest;
|
||||||
|
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitCommand;
|
||||||
|
use App\Domains\Event\Actions\SetCostUnit\SetCostUnitRequest;
|
||||||
|
use App\Enumerations\CostUnitType;
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Resources\EventResource;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use DateTime;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CreateController extends CommonController {
|
||||||
|
public function __invoke() {
|
||||||
|
return new InertiaProvider('Event/Create', [
|
||||||
|
'emailAddress' => auth()->user()->email,
|
||||||
|
'eventAccount' => $this->tenant->account_name,
|
||||||
|
'eventIban' => $this->tenant->account_iban,
|
||||||
|
'eventPayPerDay' => $this->tenant->slug === 'lv' ? true : false,
|
||||||
|
'participationFeeType' => $this->tenant->slug === 'lv' ?
|
||||||
|
ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED :
|
||||||
|
ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY,
|
||||||
|
])->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doCreate(Request $request) : JsonResponse {
|
||||||
|
|
||||||
|
$eventBegin = DateTime::createFromFormat('Y-m-d', $request->input('eventBegin'));
|
||||||
|
$eventEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventEnd'));
|
||||||
|
$eventEarlyBirdEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventEarlyBirdEnd'));
|
||||||
|
$registrationFinalEnd = DateTime::createFromFormat('Y-m-d', $request->input('eventRegistrationFinalEnd'));
|
||||||
|
$participationFeeType = ParticipationFeeType::where('slug', $request->input('eventParticipationFeeType'))->first();
|
||||||
|
$payPerDay = $request->input('eventPayPerDay');
|
||||||
|
$payDirect = $request->input('eventPayDirectly');
|
||||||
|
|
||||||
|
$billingDeadline = $eventEnd->modify('+1 month');
|
||||||
|
|
||||||
|
$createRequest = new CreateEventRequest(
|
||||||
|
$request->input('eventName'),
|
||||||
|
$request->input('eventLocation'),
|
||||||
|
$request->input('eventPostalCode'),
|
||||||
|
$request->input('eventEmail'),
|
||||||
|
$eventBegin,
|
||||||
|
$eventEnd,
|
||||||
|
$eventEarlyBirdEnd,
|
||||||
|
$registrationFinalEnd,
|
||||||
|
$request->input('eventEarlyBirdEndAmountIncrease'),
|
||||||
|
$participationFeeType,
|
||||||
|
$request->input('eventAccount'),
|
||||||
|
$request->input('eventIban'),
|
||||||
|
$payPerDay,
|
||||||
|
$payDirect
|
||||||
|
);
|
||||||
|
|
||||||
|
$wasSuccessful = false;
|
||||||
|
|
||||||
|
$createCommand = new CreateEventCommand($createRequest);
|
||||||
|
$result = $createCommand->execute();
|
||||||
|
if ($result->success) {
|
||||||
|
$createCostUnitRequest = new CreateCostUnitRequest(
|
||||||
|
$result->event->name,
|
||||||
|
CostUnitType::COST_UNIT_TYPE_EVENT,
|
||||||
|
Amount::fromString('0,25'),
|
||||||
|
true,
|
||||||
|
$billingDeadline
|
||||||
|
);
|
||||||
|
|
||||||
|
$createCostUnitCommand = new CreateCostUnitCommand($createCostUnitRequest);
|
||||||
|
$costUnitResponse = $createCostUnitCommand->execute();
|
||||||
|
|
||||||
|
if ($costUnitResponse->success) {
|
||||||
|
$costUnitUpdateRequest = new SetCostUnitRequest($result->event, $costUnitResponse->costUnit);
|
||||||
|
$costUnitUpdateCommand = new SetCostUnitCommand($costUnitUpdateRequest);
|
||||||
|
$costUnitSetResponse = $costUnitUpdateCommand->execute();
|
||||||
|
$wasSuccessful = $costUnitSetResponse->success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wasSuccessful) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'event' => new EventResource($costUnitUpdateRequest->event)->toArray()
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Die Veranstaltung konnte nicht angelegt werden.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
118
app/Domains/Event/Controllers/DetailsController.php
Normal file
118
app/Domains/Event/Controllers/DetailsController.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesCommand;
|
||||||
|
use App\Domains\Event\Actions\SetParticipationFees\SetParticipationFeesRequest;
|
||||||
|
use App\Domains\Event\Actions\UpdateEvent\UpdateEventCommand;
|
||||||
|
use App\Domains\Event\Actions\UpdateEvent\UpdateEventRequest;
|
||||||
|
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersCommand;
|
||||||
|
use App\Domains\Event\Actions\UpdateManagers\UpdateManagersRequest;
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use App\Enumerations\ParticipationType;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Resources\EventResource;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class DetailsController extends CommonController {
|
||||||
|
public function __invoke(int $eventId) {
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function summary(int $eventId) : JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
return response()->json(['event' => new EventResource($event)->toArray()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCommonSettings(int $eventId, Request $request) : JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
|
||||||
|
$earlyBirdEnd = \DateTime::createFromFormat('Y-m-d', $request->input('earlyBirdEnd'));
|
||||||
|
$registrationFinalEnd = \DateTime::createFromFormat('Y-m-d', $request->input('registrationFinalEnd'));
|
||||||
|
$flatSupport = Amount::fromString($request->input('flatSupport'));
|
||||||
|
$supportPerPerson = Amount::fromString($request->input('supportPerson'));
|
||||||
|
|
||||||
|
$contributinLocalGroups = $request->input('contributingLocalGroups');
|
||||||
|
$eatingHabits = $request->input('eatingHabits');
|
||||||
|
|
||||||
|
$eventUpdateRequest = new UpdateEventRequest(
|
||||||
|
$event,
|
||||||
|
$request->input('eventName'),
|
||||||
|
$request->input('eventLocation'),
|
||||||
|
$request->input('postalCode'),
|
||||||
|
$request->input('email'),
|
||||||
|
$earlyBirdEnd,
|
||||||
|
$registrationFinalEnd,
|
||||||
|
$request->input('alcoholicsAge'),
|
||||||
|
$request->input('sendWeeklyReports'),
|
||||||
|
$request->input('registrationAllowed'),
|
||||||
|
$flatSupport,
|
||||||
|
$supportPerPerson,
|
||||||
|
$contributinLocalGroups,
|
||||||
|
$eatingHabits
|
||||||
|
);
|
||||||
|
|
||||||
|
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
|
||||||
|
$response = $eventUpdateCommand->execute();
|
||||||
|
|
||||||
|
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateEventManagers(int $eventId, Request $request) : JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
|
||||||
|
$updateEventManagersRequest = new UpdateManagersRequest($event, $request->input('selectedManagers'));
|
||||||
|
$updateEventManagersCommand = new UpdateManagersCommand($updateEventManagersRequest);
|
||||||
|
$response = $updateEventManagersCommand->execute();
|
||||||
|
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateParticipationFees(int $eventId, Request $request) : JsonResponse {
|
||||||
|
$event = $this->events->getById($eventId);
|
||||||
|
|
||||||
|
$participationFeeFirst = [
|
||||||
|
'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||||
|
'name' => 'Teilnehmer',
|
||||||
|
'description' => $request->input('pft_1_description'),
|
||||||
|
'amount' => Amount::fromString($request->input('pft_1_amount'))
|
||||||
|
];
|
||||||
|
|
||||||
|
$participationFeeRequest = new SetParticipationFeesRequest($event, $participationFeeFirst);
|
||||||
|
|
||||||
|
if ($request->input('pft_2_active')) {
|
||||||
|
$participationFeeRequest->participationFeeSecond = [
|
||||||
|
'type' => ParticipationType::PARTICIPATION_TYPE_TEAM,
|
||||||
|
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Kernteam' : 'Solidaritätsbeitrag',
|
||||||
|
'description' => $request->input('pft_2_description'),
|
||||||
|
'amount' => Amount::fromString($request->input('pft_2_amount'))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('pft_3_active')) {
|
||||||
|
$participationFeeRequest->participationFeeThird = [
|
||||||
|
'type' => ParticipationType::PARTICIPATION_TYPE_VOLUNTEER,
|
||||||
|
'name' => $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED ? 'Unterstützende' : 'Reduzierter Beitrag',
|
||||||
|
'description' => $event->participation_fee_type !== ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY ? $request->input('pft_3_description') : 'Nach Verfügbarkeit',
|
||||||
|
'amount' => Amount::fromString($request->input('pft_3_amount'))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('pft_4_active') && $event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED) {
|
||||||
|
$participationFeeRequest->participationFeeFourth = [
|
||||||
|
'type' => ParticipationType::PARTICIPATION_TYPE_OTHER,
|
||||||
|
'name' => 'Sonstige',
|
||||||
|
'description' => $request->input('pft_4_description'),
|
||||||
|
'amount' => Amount::fromString($request->input('pft_4_amount'))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$participationFeeCommand = new SetParticipationFeesCommand($participationFeeRequest);
|
||||||
|
$response = $participationFeeCommand->excetute();
|
||||||
|
|
||||||
|
return response()->json(['status' => $response->success ? 'success' : 'error']);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Domains/Event/Routes/api.php
Normal file
31
app/Domains/Event/Routes/api.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Event\Controllers\CreateController;
|
||||||
|
use App\Domains\Event\Controllers\DetailsController;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('api/v1')
|
||||||
|
->group(function () {
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::prefix('event')->group(function () {
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::post('/create', [CreateController::class, 'doCreate']);
|
||||||
|
|
||||||
|
Route::prefix('/details/{eventId}') ->group(function () {
|
||||||
|
Route::get('/summary', [DetailsController::class, 'summary']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::post('/event-managers', [DetailsController::class, 'updateEventManagers']);
|
||||||
|
Route::post('/participation-fees', [DetailsController::class, 'updateParticipationFees']);
|
||||||
|
Route::post('/common-settings', [DetailsController::class, 'updateCommonSettings']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
15
app/Domains/Event/Routes/web.php
Normal file
15
app/Domains/Event/Routes/web.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Event\Controllers\CreateController;
|
||||||
|
use App\Domains\Event\Controllers\DetailsController;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::prefix('event')->group(function () {
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::get('/new', CreateController::class);
|
||||||
|
Route::get('/details/{eventId}', DetailsController::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
294
app/Domains/Event/Views/Create.vue
Normal file
294
app/Domains/Event/Views/Create.vue
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||||
|
import {reactive, watch, ref, computed} from 'vue'
|
||||||
|
import { subWeeks, format, parseISO, isValid, addDays } from 'date-fns'
|
||||||
|
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||||
|
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ParticipationFees from "./Partials/ParticipationFees.vue";
|
||||||
|
|
||||||
|
const { request } = useAjax();
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
'emailAddress': String,
|
||||||
|
'eventAccount': String,
|
||||||
|
'eventIban': String,
|
||||||
|
"eventPayPerDay": Boolean,
|
||||||
|
"participationFeeType": String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const errors = reactive({})
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
eventName: '',
|
||||||
|
eventPostalCode: '',
|
||||||
|
eventLocation: '',
|
||||||
|
eventEmail: props.emailAddress ? props.emailAddress : '',
|
||||||
|
eventBegin: '',
|
||||||
|
eventEnd: '',
|
||||||
|
eventEarlyBirdEnd: '',
|
||||||
|
eventEarlyBirdEndAmountIncrease: 50,
|
||||||
|
eventRegistrationFinalEnd: '',
|
||||||
|
eventAccount: props.eventAccount ? props.eventAccount : '',
|
||||||
|
eventIban: props.eventIban ? props.eventIban : '',
|
||||||
|
eventPayDirectly: true,
|
||||||
|
eventPayPerDay: props.eventPayPerDay ? props.eventPayPerDay : false,
|
||||||
|
eventParticipationFeeType: props.participationFeeType ? props.participationFeeType : 'fixed',
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => formData.eventBegin,
|
||||||
|
(newValue) => {
|
||||||
|
if (!newValue) return
|
||||||
|
|
||||||
|
const beginDate = parseISO(newValue)
|
||||||
|
|
||||||
|
if (!isValid(beginDate)) return
|
||||||
|
|
||||||
|
const fourWeeksBefore = subWeeks(beginDate, 4)
|
||||||
|
const twoWeeksBefore = subWeeks(beginDate, 2)
|
||||||
|
const threeDaysAfter = addDays(beginDate, 2)
|
||||||
|
|
||||||
|
formData.eventEarlyBirdEnd = format(
|
||||||
|
fourWeeksBefore,
|
||||||
|
'yyyy-MM-dd'
|
||||||
|
)
|
||||||
|
|
||||||
|
formData.eventRegistrationFinalEnd = format(
|
||||||
|
twoWeeksBefore,
|
||||||
|
'yyyy-MM-dd'
|
||||||
|
)
|
||||||
|
|
||||||
|
formData.eventEnd = format(
|
||||||
|
threeDaysAfter,
|
||||||
|
'yyyy-MM-dd'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const formIsValid = computed(() => {
|
||||||
|
errors.eventEmail = '';
|
||||||
|
errors.eventName = '';
|
||||||
|
errors.eventLocation = '';
|
||||||
|
errors.eventPostalCode = '';
|
||||||
|
errors.eventBegin = '';
|
||||||
|
errors.eventEnd = '';
|
||||||
|
errors.eventEarlyBirdEnd = '';
|
||||||
|
errors.eventRegistrationFinalEnd = '';
|
||||||
|
errors.eventAccount = '';
|
||||||
|
errors.eventIban = '';
|
||||||
|
|
||||||
|
var returnValue = true;
|
||||||
|
|
||||||
|
if (!formData.eventName) {
|
||||||
|
errors.eventName = 'Bitte gib den Veranstaltungsnamen ein'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventEmail) {
|
||||||
|
errors.eventEmail = 'Bitte gib die E-Mail-Adresse der Veranstaltungsleitung für Rückfragen der Teilnehmenden ein'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventLocation) {
|
||||||
|
errors.eventLocation = 'Bitte gib den Veranstaltungsort ein'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventPostalCode) {
|
||||||
|
errors.eventPostalCode = 'Bitte gib die Postleitzahl des Veranstaltungsorts ein'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventBegin) {
|
||||||
|
errors.eventBegin = 'Bitte gib das Anfangsdatum der Veranstaltung ein'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventEnd ||formData.eventEnd < formData.eventBegin ) {
|
||||||
|
errors.eventEnd = 'Das Enddatum darf nicht vor dem Anfangsdatum liegen'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventEarlyBirdEnd ||formData.eventEarlyBirdEnd > formData.eventBegin ) {
|
||||||
|
errors.eventEarlyBirdEnd = 'Das Enddatum der Early-Bird-Phase muss vor dem Veranstaltungsbeginn liegen'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventRegistrationFinalEnd ||formData.eventRegistrationFinalEnd > formData.eventBegin ) {
|
||||||
|
errors.eventRegistrationFinalEnd = 'Der Anmeldeschluss darf nicht nach dem Veranstaltungsbeginn liegen'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventAccount) {
|
||||||
|
errors.eventAccount = 'Bitte gib an, auf wen das Veranstaltungskonto für eingehende Beiträge läuft'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.eventIban) {
|
||||||
|
errors.eventIban = 'Bitte gib die IBAN des Kontos für Teilnahmebeiträge ein'
|
||||||
|
returnValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const showParticipationFees = ref(false)
|
||||||
|
const newEvent = ref(null)
|
||||||
|
|
||||||
|
async function createEvent() {
|
||||||
|
if (!formIsValid.value) return false
|
||||||
|
|
||||||
|
const data = await request("/api/v1/event/create", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
eventName: formData.eventName,
|
||||||
|
eventPostalCode: formData.eventPostalCode,
|
||||||
|
eventLocation: formData.eventLocation,
|
||||||
|
eventEmail: formData.eventEmail,
|
||||||
|
eventBegin: formData.eventBegin,
|
||||||
|
eventEnd: formData.eventEnd,
|
||||||
|
eventEarlyBirdEnd: formData.eventEarlyBirdEnd,
|
||||||
|
eventEarlyBirdEndAmountIncrease: formData.eventEarlyBirdEndAmountIncrease,
|
||||||
|
eventRegistrationFinalEnd: formData.eventRegistrationFinalEnd,
|
||||||
|
eventAccount: formData.eventAccount,
|
||||||
|
eventIban: formData.eventIban,
|
||||||
|
eventPayDirectly: formData.eventPayDirectly,
|
||||||
|
eventPayPerDay: formData.eventPayPerDay,
|
||||||
|
eventParticipationFeeType: formData.eventParticipationFeeType,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status !== 'success') {
|
||||||
|
toas.error(data.message);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log(data.event);
|
||||||
|
newEvent.value = data.event;
|
||||||
|
showParticipationFees.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finishCreation() {
|
||||||
|
window.location.href = '/event/details/' + newEvent.value.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title="Neue Veranstaltung">
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
<span style="font-weight: bolder;">Grundlegende Veranstaltungsdaten</span>
|
||||||
|
</legend>
|
||||||
|
<ParticipationFees v-if="showParticipationFees" :event="newEvent" @close="finishCreation" />
|
||||||
|
|
||||||
|
<table style="margin-top: 40px; width: 100%" v-else>
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Veranstaltungsname</th>
|
||||||
|
<td class="height-50"><input type="text" v-model="formData.eventName" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventName" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Veranstaltungsort</th>
|
||||||
|
<td class="height-50"><input type="text" v-model="formData.eventLocation" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventLocation" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Postleitzahl des Veranstaltungsorts</th>
|
||||||
|
<td class="height-50"><input type="text" v-model="formData.eventPostalCode" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventPostalCode" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">E-Mail-Adresse der Veranstaltungsleitung</th>
|
||||||
|
<td class="height-50"><input type="email" v-model="formData.eventEmail" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventEmail" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Beginn</th>
|
||||||
|
<td class="height-50"><input type="date" v-model="formData.eventBegin" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventBegin" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Ende</th>
|
||||||
|
<td class="height-50"><input type="date" v-model="formData.eventEnd" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventEnd" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Ende Early-Bird-Phase</th>
|
||||||
|
<td class="height-50"><input type="date" v-model="formData.eventEarlyBirdEnd" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventEarlyBirdEnd" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Finaler Anmeldeschluss</th>
|
||||||
|
<td class="height-50"><input type="date" v-model="formData.eventRegistrationFinalEnd" class="width-half-full" />
|
||||||
|
<ErrorText :message="errors.eventRegistrationFinalEnd" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Beitragsart</th>
|
||||||
|
<td class="height-50">
|
||||||
|
<select v-model="formData.eventParticipationFeeType" class="width-half-full">
|
||||||
|
<option value="fixed">Festpreis</option>
|
||||||
|
<option value="solidarity">Solidaritätsprinzip</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Preiserhöhung nach Early-Bird-Phase</th>
|
||||||
|
<td class="height-50"><input type="number" v-model="formData.eventEarlyBirdEndAmountIncrease" class="width-tiny" />%</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Veranstsaltungs-Konto</th>
|
||||||
|
<td class="height-50"><input type="text" v-model="formData.eventAccount" class="width-full" />
|
||||||
|
<ErrorText :message="errors.eventAccount" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<th class="width-medium pr-20 height-50">Veranstaltungs-IBAN</th>
|
||||||
|
<td class="height-50"><input type="text" v-model="formData.eventIban" class="width-full" />
|
||||||
|
<ErrorText :message="errors.eventIban" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<td colspan="2" style="font-weight: bold;">
|
||||||
|
<input type="checkbox" v-model="formData.eventPayDirectly">
|
||||||
|
Teilnehmende zahlen direkt aufs Veranstaltungskonto
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="vertical-align: top;">
|
||||||
|
<td colspan="2" style="font-weight: bold;">
|
||||||
|
<input type="checkbox" v-model="formData.eventPayPerDay">
|
||||||
|
Beitrag abhängig von Anwesenheitstagen
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="pt-20">
|
||||||
|
<input type="button" value="Veranstaltung erstellen" @click="createEvent" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
60
app/Domains/Event/Views/Details.vue
Normal file
60
app/Domains/Event/Views/Details.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup>
|
||||||
|
import {reactive, inject, onMounted} from 'vue';
|
||||||
|
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||||
|
import ListCostUnits from "../../CostUnit/Views/Partials/ListCostUnits.vue";
|
||||||
|
import Overview from "./Partials/Overview.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
event: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: 'Übersicht',
|
||||||
|
component: Overview,
|
||||||
|
endpoint: "/api/v1/event/details/" + props.event.id + '/summary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Alle Teilnehmendenden',
|
||||||
|
component: ListCostUnits,
|
||||||
|
endpoint: "/api/v1/cost-unit/open/current-running-jobs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Teilis nach Stamm',
|
||||||
|
component: ListCostUnits,
|
||||||
|
endpoint: "/api/v1/cost-unit/open/closed-cost-units",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Teilis nach Teili-Gruppe',
|
||||||
|
component: ListCostUnits,
|
||||||
|
endpoint: "/api/v1/cost-unit/open/archived-cost-units",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Abgemeldete Teilis',
|
||||||
|
component: ListCostUnits,
|
||||||
|
endpoint: "/api/v1/cost-unit/open/archived-cost-units",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Zusätze',
|
||||||
|
component: ListCostUnits,
|
||||||
|
endpoint: "/api/v1/cost-unit/open/archived-cost-units",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.message) {
|
||||||
|
toast.success(props.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout :title="'Veranstaltungsdetails ' + props.event.name">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<tabbed-page :tabs="tabs" />
|
||||||
|
|
||||||
|
</shadowed-box>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
258
app/Domains/Event/Views/Partials/CommonSettings.vue
Normal file
258
app/Domains/Event/Views/Partials/CommonSettings.vue
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
<script setup>
|
||||||
|
import {onMounted, reactive, ref} from "vue";
|
||||||
|
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||||
|
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||||
|
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
event: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const dynmicProps = reactive({
|
||||||
|
localGroups: [],
|
||||||
|
eatingHabits:[]
|
||||||
|
});
|
||||||
|
|
||||||
|
const contributingLocalGroups = ref([])
|
||||||
|
const eatingHabits = ref([]);
|
||||||
|
|
||||||
|
const errors = reactive({})
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
contributingLocalGroups: contributingLocalGroups.value,
|
||||||
|
eventName: props.event.name,
|
||||||
|
eventLocation: props.event.location,
|
||||||
|
postalCode: props.event.postalCode,
|
||||||
|
email: props.event.email,
|
||||||
|
earlyBirdEnd: props.event.earlyBirdEnd.internal,
|
||||||
|
registrationFinalEnd: props.event.registrationFinalEnd.internal,
|
||||||
|
alcoholicsAge: props.event.alcoholicsAge,
|
||||||
|
eatingHabits: eatingHabits.value,
|
||||||
|
sendWeeklyReports: props.event.sendWeeklyReports,
|
||||||
|
registrationAllowed: props.event.registrationAllowed,
|
||||||
|
flatSupport: props.event.flatSupportEdit,
|
||||||
|
supportPerson: props.event.supportPersonEdit,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-event-setting-data');
|
||||||
|
const data = await response.json();
|
||||||
|
Object.assign(dynmicProps, data);
|
||||||
|
|
||||||
|
contributingLocalGroups.value = props.event.contributingLocalGroups?.map(t => t.id) ?? []
|
||||||
|
eatingHabits.value = props.event.eatingHabits?.map(t => t.id) ?? []
|
||||||
|
});
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const response = await request('/api/v1/event/details/' + props.event.id + '/common-settings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
eventName: formData.eventName,
|
||||||
|
eventLocation: formData.eventLocation,
|
||||||
|
postalCode: formData.postalCode,
|
||||||
|
email: formData.email,
|
||||||
|
earlyBirdEnd: formData.earlyBirdEnd,
|
||||||
|
registrationFinalEnd: formData.registrationFinalEnd,
|
||||||
|
alcoholicsAge: formData.alcoholicsAge,
|
||||||
|
sendWeeklyReports: formData.sendWeeklyReports,
|
||||||
|
registrationAllowed: formData.registrationAllowed,
|
||||||
|
flatSupport: formData.flatSupport,
|
||||||
|
supportPerson: formData.supportPerson,
|
||||||
|
contributingLocalGroups: contributingLocalGroups.value,
|
||||||
|
eatingHabits: eatingHabits.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 'success') {
|
||||||
|
toast.success('Einstellungen wurden erfolgreich gespeichert.')
|
||||||
|
emit('close')
|
||||||
|
} else {
|
||||||
|
toast.error('Beim Speichern ist ein Fehler aufgetreten.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h2>Einstellungen</h2>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row top">
|
||||||
|
<div class="left">
|
||||||
|
<table class="event-settings-table" style="width: 80%;">
|
||||||
|
<tr>
|
||||||
|
<th>Veranstaltungsname</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.eventName" class="width-full" /><br />
|
||||||
|
<ErrorText :message="errors.eventName" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Veranstaltungsort</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.eventLocation" class="width-full" /><br />
|
||||||
|
<ErrorText :message="errors.eventLocation" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Postleitzahl des Veranstaltungsorts</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.postalCode" class="width-full" /><br />
|
||||||
|
<ErrorText :message="errors.eventPostalCode" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>E-Mail-Adresse der Veranstaltungsleitung</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="formData.email" class="width-full" /><br />
|
||||||
|
<ErrorText :message="errors.eventEmail" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Ende der EarlyBird-Phase</th>
|
||||||
|
<td>
|
||||||
|
<input type="date" v-model="formData.earlyBirdEnd" class="width-full" /><br />
|
||||||
|
<ErrorText :message="errors.earlyBirdEnd" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Finaler Anmeldeschluss</th>
|
||||||
|
<td>
|
||||||
|
<input type="date" v-model="formData.registrationFinalEnd" class="width-full" /><br />
|
||||||
|
<ErrorText :message="errors.registrationFinalEnd" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Fördermittel</th>
|
||||||
|
<td>
|
||||||
|
<amountInput v-model="formData.supportPerson" clasS="width-small" /> Euro p.P. / Tag
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Zuschüsse</th>
|
||||||
|
<td>
|
||||||
|
<amountInput v-model="formData.flatSupport" clasS="width-small" /> Euro pauschal
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Mindestalter für Alkoholkonsum</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" v-model="formData.alcoholicsAge" class="width-tiny" /><br />
|
||||||
|
<ErrorText :message="errors.alcoholicsAge" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="height: 25px !important;">
|
||||||
|
<input type="checkbox" v-model="formData.sendWeeklyReports" id="sendWeeklyReports" />
|
||||||
|
<label for="sendWeeklyReports">Wöchentliche Zusammenfassung per E-Mail an Stämme schicken</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="checkbox" v-model="formData.registrationAllowed" id="registrationAllowed" />
|
||||||
|
<label for="registrationAllowed">Veranstaltung ist für Anmeldungen geöffnet</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Teilnehmende Stämme</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="localGroup in dynmicProps.localGroups">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" :id="'localgroup_' + localGroup.id" :value="localGroup.id" v-model="contributingLocalGroups" />
|
||||||
|
<label style="padding-left: 5px;" :for="'localgroup_' + localGroup.id">{{localGroup.name}}</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th style="padding-top: 40px !important;">Angebotene Ernährung</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="eatingHabit in dynmicProps.eatingHabits">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" :id="'eatinghabit' + eatingHabit.id" :value="eatingHabit.id" v-model="eatingHabits" />
|
||||||
|
<label style="padding-left: 5px;" :for="'eatinghabit' + eatingHabit.id">{{eatingHabit.name}}</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row bott">
|
||||||
|
<input type="button" value="Speichern" @click="save" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px; /* Abstand zwischen den Zeilen */
|
||||||
|
width: 95%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px; /* Abstand zwischen den Spalten */
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.top .left {
|
||||||
|
flex: 0 0 70%; /* feste Breite von 80% */
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.top .right {
|
||||||
|
flex: 0 0 30%; /* feste Breite von 20% */
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.bottom {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-settings-table {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-settings-table tr {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-settings-table td {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-settings-table th {
|
||||||
|
vertical-align: top;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
63
app/Domains/Event/Views/Partials/EventManagement.vue
Normal file
63
app/Domains/Event/Views/Partials/EventManagement.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {onMounted, reactive, ref} from "vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||||
|
const selectedManagers = ref([])
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
event: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const commonProps = reactive({
|
||||||
|
activeUsers: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||||
|
const data = await response.json();
|
||||||
|
Object.assign(commonProps, data);
|
||||||
|
|
||||||
|
selectedManagers.value = props.event.managers?.map(t => t.id) ?? []
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function updateManagers() {
|
||||||
|
|
||||||
|
const response = await request('/api/v1/event/details/' + props.event.id + '/event-managers', {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
selectedManagers: selectedManagers.value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 'success') {
|
||||||
|
toast.success('Einstellungen wurden erfolgreich gespeichert.')
|
||||||
|
emit('close')
|
||||||
|
} else {
|
||||||
|
toast.error('Beim Speichern ist ein Fehler aufgetreten.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h3>Aktionsleitung:</h3>
|
||||||
|
<p v-for="user in commonProps.activeUsers">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:id="'user_' + user.id"
|
||||||
|
:value="user.id"
|
||||||
|
v-model="selectedManagers"
|
||||||
|
/>
|
||||||
|
<label :for="'user_' + user.id">{{user.fullname}}</label>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input type="button" value="Speichern" @click="updateManagers" />
|
||||||
|
</template>
|
||||||
|
|
||||||
113
app/Domains/Event/Views/Partials/Overview.vue
Normal file
113
app/Domains/Event/Views/Partials/Overview.vue
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<script setup>
|
||||||
|
import {onMounted, reactive, ref} from "vue";
|
||||||
|
import ParticipationFees from "./ParticipationFees.vue";
|
||||||
|
import ParticipationSummary from "./ParticipationSummary.vue";
|
||||||
|
import CommonSettings from "./CommonSettings.vue";
|
||||||
|
import EventManagement from "./EventManagement.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const dynamicProps = reactive({
|
||||||
|
event : null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayData = ref('main');
|
||||||
|
|
||||||
|
async function showMain() {
|
||||||
|
const response = await fetch("/api/v1/event/details/" + props.data.event.id + '/summary');
|
||||||
|
const data = await response.json();
|
||||||
|
Object.assign(dynamicProps, data);
|
||||||
|
displayData.value = 'main';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showCommonSettings() {
|
||||||
|
displayData.value = 'commonSettings';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showParticipationFees() {
|
||||||
|
displayData.value = 'participationFees';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showEventManagement() {
|
||||||
|
displayData.value = 'eventManagement';
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await fetch("/api/v1/event/details/" + props.data.event.id + '/summary');
|
||||||
|
const data = await response.json();
|
||||||
|
Object.assign(dynamicProps, data);
|
||||||
|
|
||||||
|
console.log(dynamicProps.event)
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ParticipationFees v-if="displayData === 'participationFees'" :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" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="event-flexbox" v-else>
|
||||||
|
<div class="event-flexbox-row top">
|
||||||
|
<div class="left"><ParticipationSummary :event="dynamicProps.event" /></div>
|
||||||
|
<div class="right">
|
||||||
|
<input type="button" value="Erste-Hilfe-Liste (PDF)" /><br/>
|
||||||
|
<input type="button" value="Teili-Liste (CSV)" /><br/>
|
||||||
|
<input type="button" value="KSV-Daten (CSV)" /><br/>
|
||||||
|
<input type="button" value="Küchenübersicht (PDF)" /><br/>
|
||||||
|
<input type="button" value="Beitragsliste (PDF)" /><br/>
|
||||||
|
<input type="button" value="Getränkeliste (PDF)" /><br/>
|
||||||
|
<input type="button" value="Foto-Erlaubnis (PDF)" /><br/>
|
||||||
|
<input type="button" class="fix-button" value="Zahlungserinnerung senden" /><br/>
|
||||||
|
<input type="button" class="deny-button" value="Letzte Mahnung senden" /><br/>
|
||||||
|
<input type="button" value="Rundmail senden" /><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="event-flexbox-row bottom">
|
||||||
|
<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="showParticipationFees">Teilnahmegebühren</label>
|
||||||
|
<a style="font-size: 9pt;" class="link" :href="'/cost-unit/' + props.data.event.costUnit.id">Ausgabenübersicht</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.event-flexbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-flexbox-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px; /* Abstand zwischen den Spalten */
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-flexbox-row.top .left {
|
||||||
|
flex: 0 0 calc(100% - 300px);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-flexbox-row.top .right {
|
||||||
|
flex: 0 0 250px; /* feste Breite von 20% */
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-flexbox-row.bottom {
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-flexbox-row.top .right input[type="button"] {
|
||||||
|
width: 100% !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
231
app/Domains/Event/Views/Partials/ParticipationFees.vue
Normal file
231
app/Domains/Event/Views/Partials/ParticipationFees.vue
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from "../../../../../resources/js/layouts/AppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import {reactive, watch} from "vue";
|
||||||
|
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||||
|
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
event: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const errors = reactive({})
|
||||||
|
const formData = reactive({
|
||||||
|
"pft_1_active": true,
|
||||||
|
"pft_1_amount": props.event.participationFee_1.amount,
|
||||||
|
"pft_1_description": props.event.participationFee_1.description,
|
||||||
|
|
||||||
|
"pft_2_active": props.event.participationFee_2.active,
|
||||||
|
"pft_2_amount": props.event.participationFee_2.amount,
|
||||||
|
"pft_2_description": props.event.participationFee_2.description,
|
||||||
|
|
||||||
|
"pft_3_active": props.event.participationFee_3.active,
|
||||||
|
"pft_3_amount": props.event.participationFee_3.amount,
|
||||||
|
"pft_3_description": props.event.participationFee_3.description,
|
||||||
|
|
||||||
|
"pft_4_active": props.event.participationFee_4.active,
|
||||||
|
"pft_4_amount": props.event.participationFee_4.amount,
|
||||||
|
"pft_4_description": props.event.participationFee_4.description,
|
||||||
|
|
||||||
|
'maxAmount': props.event.maxAmount,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
function validateInput() {
|
||||||
|
var noErrors = true;
|
||||||
|
|
||||||
|
if (formData.pft_1_description === '' && !props.event.solidarityPayment) {
|
||||||
|
errors.pft_1_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||||
|
noErrors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.pft_2_description === '' && formData.pft_2_active && !props.event.solidarityPayment) {
|
||||||
|
errors.pft_2_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||||
|
noErrors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.pft_3_description === '' && formData.pft_3_active && !props.event.solidarityPayment) {
|
||||||
|
errors.pft_3_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||||
|
noErrors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.pft_4_description === '' && formData.pft_4_active) {
|
||||||
|
errors.pft_4_description = 'Eine Beschreibung für diese Gruppe ist erforderlich';
|
||||||
|
noErrors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return noErrors;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveParticipationFees() {
|
||||||
|
if (!validateInput()) {
|
||||||
|
toast.error('Bitte prüfe alle Eingaben auf Fehler')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request('/api/v1/event/details/' + props.event.id + '/participation-fees', {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
event_id: props.event.id,
|
||||||
|
pft_1_active: formData.pft_1_active,
|
||||||
|
pft_1_amount: formData.pft_1_amount,
|
||||||
|
pft_1_description: formData.pft_1_description,
|
||||||
|
|
||||||
|
pft_2_active: formData.pft_2_active,
|
||||||
|
pft_2_amount: formData.pft_2_amount,
|
||||||
|
pft_2_description: formData.pft_2_description,
|
||||||
|
|
||||||
|
pft_3_active: formData.pft_3_active,
|
||||||
|
pft_3_amount: formData.pft_3_amount,
|
||||||
|
pft_3_description: formData.pft_3_description,
|
||||||
|
|
||||||
|
pft_4_active: formData.pft_4_active,
|
||||||
|
pft_4_amount: formData.pft_4_amount,
|
||||||
|
pft_4_description: formData.pft_4_description,
|
||||||
|
|
||||||
|
maxAmount: formData.maxAmount,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalculateMaxAmount(newValue) {
|
||||||
|
if (formData.maxAmount === 0) return;
|
||||||
|
|
||||||
|
var newAmount = parseFloat(newValue.replace(',', '.'));
|
||||||
|
if (props.event.payPerDay) {
|
||||||
|
newAmount = newAmount * props.event.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMaxAmount = formData.maxAmount.replace(',', '.');
|
||||||
|
|
||||||
|
if (newAmount > currentMaxAmount) {
|
||||||
|
formData.maxAmount = newAmount.toFixed(2).replace('.', ',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<table style="width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td>Aktiv</td>
|
||||||
|
<td>Preisgruppe</td>
|
||||||
|
<td>Betrag</td>
|
||||||
|
<td>Beschreibung</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="height: 65px; vertical-align: top">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" v-model="formData.participationFeeType_1" checked disabled/>
|
||||||
|
</td>
|
||||||
|
<td v-if="props.event.solidarityPayment">
|
||||||
|
Regulärer Beitrag
|
||||||
|
</td>
|
||||||
|
<td v-else>
|
||||||
|
Teilnehmende
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<AmountInput v-model="formData.pft_1_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_1_amount)" />
|
||||||
|
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||||
|
<label v-else> Euro Gesamt</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input v-if="!props.event.solidarityPayment" type="text" v-model="formData.pft_1_description" style="width: 300px;" />
|
||||||
|
<label v-else></label>
|
||||||
|
<ErrorText :message="errors.pft_1_description" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 65px; vertical-align: top;">
|
||||||
|
<td>
|
||||||
|
<input id="use_pft_2" type="checkbox" v-model="formData.pft_2_active" :checked="formData.pft_2_active" />
|
||||||
|
</td>
|
||||||
|
<td v-if="props.event.solidarityPayment">
|
||||||
|
<label for="use_pft_2" style="cursor: default">
|
||||||
|
Solidaritätsbeitrag
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td v-else>
|
||||||
|
<label for="use_pft_2" style="cursor: default">
|
||||||
|
Kernteam
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td v-if="formData.pft_2_active">
|
||||||
|
<AmountInput v-model="formData.pft_2_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_2_amount)" />
|
||||||
|
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||||
|
<label v-else> Euro Gesamt</label>
|
||||||
|
</td>
|
||||||
|
<td v-if="formData.pft_2_active">
|
||||||
|
<input v-if="!props.event.solidarityPayment" type="text" v-model="formData.pft_2_description" style="width: 300px;" />
|
||||||
|
<label v-else></label>
|
||||||
|
<ErrorText :message="errors.pft_2_description" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 65px; vertical-align: top;">
|
||||||
|
<td>
|
||||||
|
<input id="use_pft_3" type="checkbox" v-model="formData.pft_3_active" :checked="formData.pft_3_active" />
|
||||||
|
</td>
|
||||||
|
<td v-if="props.event.solidarityPayment">
|
||||||
|
<label for="use_pft_3" style="cursor: default">
|
||||||
|
Reduzierter Beitrag
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td v-else>
|
||||||
|
<label for="use_pft_3" style="cursor: default">
|
||||||
|
Unterstützende
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td v-if="formData.pft_3_active">
|
||||||
|
<AmountInput v-model="formData.pft_3_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_3_amount)" />
|
||||||
|
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||||
|
<label v-else> Euro Gesamt</label>
|
||||||
|
</td>
|
||||||
|
<td v-if="formData.pft_3_active">
|
||||||
|
<input v-if="!props.event.solidarityPayment" type="text" v-model="formData.pft_3_description" style="width: 300px;" />
|
||||||
|
<label v-else>Nach Verfügbarkeit</label>
|
||||||
|
<ErrorText :message="errors.pft_3_description" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 65px; vertical-align: top;" v-if="!props.event.solidarityPayment">
|
||||||
|
<td>
|
||||||
|
<input id="use_pft_4" type="checkbox" v-model="formData.pft_4_active" :checked="formData.pft_4_active" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label for="use_pft_4" style="cursor: default">
|
||||||
|
Sonstige
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td v-if="formData.pft_4_active">
|
||||||
|
<AmountInput v-model="formData.pft_4_amount" class="width-small" @blur="recalculateMaxAmount(formData.pft_4_amount)" />
|
||||||
|
<label v-if="props.event.payPerDay"> Euro / Tag</label>
|
||||||
|
<label v-else> Euro Gesamt</label>
|
||||||
|
</td>
|
||||||
|
<td v-if="formData.pft_4_active">
|
||||||
|
<input type="text" v-model="formData.pft_4_description" style="width: 300px;" />
|
||||||
|
<ErrorText :message="errors.pft_4_description" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Maximaler Beitrag für Veranstaltung:
|
||||||
|
</td>
|
||||||
|
<td colspan="2">
|
||||||
|
<AmountInput v-model="formData.maxAmount" class="width-small" /> Euro Gesamt
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">
|
||||||
|
<input type="button" value="Speichern" @click="saveParticipationFees" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
125
app/Domains/Event/Views/Partials/ParticipationSummary.vue
Normal file
125
app/Domains/Event/Views/Partials/ParticipationSummary.vue
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
event: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h2>Übersicht</h2>
|
||||||
|
<div class="participant-flexbox">
|
||||||
|
<div class="participant-flexbox-row top">
|
||||||
|
<div class="left">
|
||||||
|
<h3>Teilnehmende</h3>
|
||||||
|
<table class="participant-income-table" style="margin-bottom: 40px;">
|
||||||
|
<tr>
|
||||||
|
<th>Teili</th>
|
||||||
|
<td>7 Personen:</td>
|
||||||
|
<td>35,00 Euro</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Teili</th>
|
||||||
|
<td>7 Personen:</td>
|
||||||
|
<td>35,00 Euro</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Teili</th>
|
||||||
|
<td>7 Personen:</td>
|
||||||
|
<td>35,00 Euro</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Sonstiges</th>
|
||||||
|
<td>{{ props.event.flatSupport }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th style="padding-bottom: 20px" colspan="2">Förderung</th>
|
||||||
|
<td style="padding-bottom: 20px">
|
||||||
|
{{ props.event.supportPersonCalced }}<br />
|
||||||
|
<label style="font-size: 9pt;">({{ props.event.supportPerson }} / Tag p.P.)</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" style="border-width: 1px; border-bottom-style: solid">Gesamt</th>
|
||||||
|
<td style="font-weight: bold; border-width: 1px; border-bottom-style: solid">{{ props.event.totalIncome }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="color:#4caf50;" v-if="props.event.totalBalance.value >= 0">
|
||||||
|
<th style="padding-top: 20px; font-size: 12pt !important;" colspan="2">Bilanz</th>
|
||||||
|
<td style="font-weight: bold; padding-top: 20px; font-size: 12pt !important;">{{ props.event.totalBalance.text }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="color:#f44336;" v-else>
|
||||||
|
<th style="padding-top: 20px; font-size: 12pt !important;" colspan="2">Bilanz</th>
|
||||||
|
<td style="font-weight: bold; padding-top: 20px; font-size: 12pt !important;">{{ props.event.totalBalance.text }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<strong>Anmelde-URL: {{props.event.url}}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right">
|
||||||
|
<h3>Ausgaben</h3>
|
||||||
|
<table class="event-payment-table">
|
||||||
|
<tr v-for="amount in props.event.costUnit.amounts">
|
||||||
|
<th>{{amount.name}}</th>
|
||||||
|
<td>{{amount.string}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 20px">Gesamt</th>
|
||||||
|
<td style="color:#f44336; border-width: 1px; border-bottom-style: solid; padding-top: 20px; font-weight: bold">{{props.event.costUnit.overAllAmount.text}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.participant-flexbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-flexbox-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px; /* Abstand zwischen den Spalten */
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-flexbox-row.top .left {
|
||||||
|
flex: 0 0 50%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-flexbox.top .right {
|
||||||
|
flex: 0 0 50%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-income-table,
|
||||||
|
.event-payment-table {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-income-table th {
|
||||||
|
width: 20px;
|
||||||
|
font-size: 11pt !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-income-table tr td:first-child {
|
||||||
|
width: 25px !important;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -36,9 +36,13 @@ class ChangeStatusCommand {
|
|||||||
$this->request->invoice->comment = $this->request->invoice->denied_reason;
|
$this->request->invoice->comment = $this->request->invoice->denied_reason;
|
||||||
$this->request->invoice->denied_reason = null;
|
$this->request->invoice->denied_reason = null;
|
||||||
break;
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_DELETED:
|
||||||
|
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DELETED;
|
||||||
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_EXPORTED:
|
||||||
|
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_EXPORTED;
|
||||||
|
$this->request->invoice->upload_required = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->invoice->save()) {
|
if ($this->request->invoice->save()) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Invoice\Actions\ChangeStatus;
|
|||||||
|
|
||||||
class ChangeStatusResponse {
|
class ChangeStatusResponse {
|
||||||
public bool $success;
|
public bool $success;
|
||||||
|
public string $invoiceReceipt;
|
||||||
|
|
||||||
function __construct() {
|
function __construct() {
|
||||||
$this->success = false;
|
$this->success = false;
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\CreateInvoiceReceipt;
|
||||||
|
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Models\PageText;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Providers\PdfMergeProvider;
|
||||||
|
use App\Resources\InvoiceResource;
|
||||||
|
use Dompdf\Dompdf;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class CreateInvoiceReceiptCommand {
|
||||||
|
private CreateInvoiceReceiptRequest $request;
|
||||||
|
private string $tempDirectory;
|
||||||
|
|
||||||
|
public function __construct(CreateInvoiceReceiptRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
$this->tempDirectory = Tenant::getTempDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : CreateInvoiceReceiptResponse {
|
||||||
|
$response = new CreateInvoiceReceiptResponse();
|
||||||
|
|
||||||
|
$filename = $this->tempDirectory . $this->request->invoice->invoice_number . '.pdf';
|
||||||
|
if (!Storage::exists($this->tempDirectory)) {
|
||||||
|
Storage::makeDirectory(Tenant::getTempDirectory() . '/' . $this->tempDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
$receipt = $this->request->invoice->document_filename;
|
||||||
|
if ($receipt === null) {
|
||||||
|
Storage::put(
|
||||||
|
$filename,
|
||||||
|
$this->createPdf( $this->createHtml(), 'portrait', $filename, false )
|
||||||
|
);
|
||||||
|
|
||||||
|
$response->fileName = $filename;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = (string)rand(1000000000, 9999999999);
|
||||||
|
$pdf_data = $this->createPdf( $this->createHtml(), 'portrait', $filename, false );
|
||||||
|
|
||||||
|
$tmpFileName = $this->tempDirectory . 'tmp-' . $token . '.pdf';
|
||||||
|
Storage::put($tmpFileName, $pdf_data);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$merger = new PdfMergeProvider();
|
||||||
|
|
||||||
|
$merger
|
||||||
|
->add( storage_path('app/private/' . $tmpFileName ))
|
||||||
|
->add(storage_path('app/private/' . $receipt))
|
||||||
|
->merge(storage_path('app/private/' .$filename) );
|
||||||
|
|
||||||
|
$response->fileName = $filename;
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$zip->open(
|
||||||
|
storage_path('app/private/' .$filename) . '.zip',
|
||||||
|
ZipArchive::CREATE | ZipArchive::OVERWRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
$zip->addFile(storage_path('app/private/' . $tmpFileName ), 'antrag.pdf');
|
||||||
|
$zip->addFile(storage_path('app/private/' . $receipt), 'beleg.pdf');
|
||||||
|
|
||||||
|
$zip->close();
|
||||||
|
$response->fileName = $filename . '.zip';
|
||||||
|
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
Storage::delete($tmpFileName);
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private function createHtml() : string {
|
||||||
|
|
||||||
|
|
||||||
|
$invoiceReadable = new InvoiceResource($this->request->invoice)->toArray();
|
||||||
|
|
||||||
|
|
||||||
|
$travelPartTemplate = <<<HTML
|
||||||
|
<tr><td>Reiseweg:</td><td>%1\$s</td></tr>
|
||||||
|
<tr><td>Gesamtlänge der Strecke:</td><td>%2\$s km x %3\$s / km</td></tr>
|
||||||
|
<tr><td>Materialtransport:</td><td>%4\$s</td></tr>
|
||||||
|
<tr><td>Mitfahrende im PKW:</td><td>%5\$s</td></tr>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
$flatTravelPart = sprintf(
|
||||||
|
$travelPartTemplate,
|
||||||
|
$invoiceReadable['travelDirection'] ,
|
||||||
|
$invoiceReadable['distance'],
|
||||||
|
$invoiceReadable['distanceAllowance'],
|
||||||
|
$invoiceReadable['transportation'],
|
||||||
|
$invoiceReadable['passengers']
|
||||||
|
);
|
||||||
|
|
||||||
|
$invoiceTravelPart = '<tr><td>Kosten für ÖPNV:</td><td>' . $invoiceReadable['amount'] . '</td></tr>';;
|
||||||
|
$expensePart = '<tr><td>Auslagenerstattung:</td><td>' . $invoiceReadable['amount'] . '</td></tr>';
|
||||||
|
|
||||||
|
$content = <<<HTML
|
||||||
|
<html>
|
||||||
|
<body style="margin-left: 20mm; margin-top: 17mm">
|
||||||
|
<h3>Abrechnungstyp %1\$s</h3><br /><br />
|
||||||
|
|
||||||
|
|
||||||
|
<table style="width: 100%%;">
|
||||||
|
<tr><td>Abrechnungsnummer:</td><td>%2\$s</td></tr>
|
||||||
|
<tr><td>Name:</td><td>%3\$s</td></tr>
|
||||||
|
<tr><td>E-Mail:</td><td>%4\$s</td></tr>
|
||||||
|
<tr><td>Telefon:</td><td>%5\$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>Wird der Betrag gespendet:</td><td>%8\$s</td></tr>
|
||||||
|
%9\$s
|
||||||
|
<tr style="font-weight: bold;">
|
||||||
|
<td style="border-bottom-width: 1px; border-bottom-style: double;">
|
||||||
|
Gesamtbetrag:
|
||||||
|
</td>
|
||||||
|
<td style="border-bottom-width: 1px; border-bottom-style: double;">
|
||||||
|
%10\$s
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td colspan="2"><br /><br /></td></tr>
|
||||||
|
%11\$s
|
||||||
|
<tr><td>Beleg digital eingereicht am:</td><td>%12\$s</td></tr>
|
||||||
|
<tr><td>Beleg akzeptiert am:</td><td>%13\$s</td></tr>
|
||||||
|
<tr><td>Beleg akzeptiert von:</td><td>%14\$s</td></tr>
|
||||||
|
</table>
|
||||||
|
%15\$s
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
switch ($this->request->invoice->type) {
|
||||||
|
case InvoiceType::INVOICE_TYPE_TRAVELLING:
|
||||||
|
$paymentType = $this->request->invoice->distance !== null ? $flatTravelPart : $invoiceTravelPart;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$paymentType = $expensePart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->invoice->donation) {
|
||||||
|
$paymentInformation = '<tr><td colspan="2">' . PageText::where('name', 'CONFIRMATION_DONATE')->first()->content . '</td></tr>';
|
||||||
|
} else {
|
||||||
|
if ($this->request->invoice->contact_bank_iban === null) {
|
||||||
|
$paymentInformation = '';
|
||||||
|
} else {
|
||||||
|
$paymentInformationTemplate = <<<HTML
|
||||||
|
<tr><td colspan="2">%1\$s</td></tr>
|
||||||
|
<tr><td>Kontoinhaber*in:</td><td>%2\$s</td></tr>
|
||||||
|
<tr><td>INAN:</td><td>%3\$s</td></tr>
|
||||||
|
<tr><td colspan="2"><br /><br /></td></tr>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
$paymentInformation = sprintf(
|
||||||
|
$paymentInformationTemplate,
|
||||||
|
PageText::where('name', 'CONFIRMATION_PAYMENT')->first()->content,
|
||||||
|
$invoiceReadable['accountOwner'],
|
||||||
|
$invoiceReadable['accountIban']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$changes = $this->request->invoice->changes !== null ? '<p>' . $this->request->invoice->changes . '</p>' : '';
|
||||||
|
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
$content,
|
||||||
|
$invoiceReadable['invoiceTypeShort'],
|
||||||
|
$invoiceReadable['invoiceNumber'],
|
||||||
|
$invoiceReadable['contactName'],
|
||||||
|
$invoiceReadable['contactEmail'],
|
||||||
|
$invoiceReadable['contactPhone'],
|
||||||
|
$invoiceReadable['costUnitName'],
|
||||||
|
$invoiceReadable['invoiceType'],
|
||||||
|
$invoiceReadable['donationText'],
|
||||||
|
$paymentType,
|
||||||
|
$invoiceReadable['amount'],
|
||||||
|
$paymentInformation,
|
||||||
|
$invoiceReadable['createdAt'],
|
||||||
|
$invoiceReadable['approvedAt'],
|
||||||
|
$invoiceReadable['approvedBy'],
|
||||||
|
$changes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createPdf( string $htmlfile, string $orientation, string $filename, bool $download = true ) {
|
||||||
|
|
||||||
|
$dompdf = new Dompdf();
|
||||||
|
$dompdf->loadHtml( $htmlfile, 'UTF-8' );
|
||||||
|
$dompdf->setPaper( 'A4', $orientation );
|
||||||
|
|
||||||
|
$dompdf->render();
|
||||||
|
if ( ! $download ) {
|
||||||
|
return $dompdf->output();
|
||||||
|
}
|
||||||
|
$dompdf->stream( $filename );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\CreateInvoiceReceipt;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
|
||||||
|
class CreateInvoiceReceiptRequest {
|
||||||
|
public Invoice $invoice;
|
||||||
|
|
||||||
|
public function __construct(Invoice $invoice) {
|
||||||
|
$this->invoice = $invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\CreateInvoiceReceipt;
|
||||||
|
|
||||||
|
class CreateInvoiceReceiptResponse {
|
||||||
|
public string $fileName;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->fileName = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
|||||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
class UpdateInvoiceCommand {
|
class UpdateInvoiceCommand {
|
||||||
private UpdateInvoiceRequest $request;
|
private UpdateInvoiceRequest $request;
|
||||||
@@ -15,11 +16,26 @@ class UpdateInvoiceCommand {
|
|||||||
|
|
||||||
public function execute() : UpdateInvoiceResponse {
|
public function execute() : UpdateInvoiceResponse {
|
||||||
$response = new UpdateInvoiceResponse();
|
$response = new UpdateInvoiceResponse();
|
||||||
|
$changes = $this->request->invoice->changes ?? '';
|
||||||
|
|
||||||
|
if ($this->request->invoice->amount !== $this->request->amount->getAmount()) {
|
||||||
|
$changes .= 'Betrag geändert von ' . Amount::fromString($this->request->invoice->amount)->toString() . ' auf ' . Amount::fromString($this->request->amount->getAmount())->toString() . '.<br />';
|
||||||
$this->request->invoice->amount = $this->request->amount->getAmount();
|
$this->request->invoice->amount = $this->request->amount->getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->invoice->invoiceType()->slug !== $this->request->invoiceType->slug) {
|
||||||
|
$changes .= 'Abrechnungstyp geändert von ' . $this->request->invoice->invoiceType()->name . ' auf ' . $this->request->invoiceType->name . '.<br />';
|
||||||
|
$this->request->invoice->type = $this->request->invoiceType->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->invoice->costUnit()->first()->id !== $this->request->costUnit->id) {
|
||||||
|
$changes .= 'Kostenstelle geändert von ' . $this->request->invoice->costUnit()->first()->name . ' auf ' . $this->request->costUnit->name . '.<br />';
|
||||||
$this->request->invoice->cost_unit_id = $this->request->costUnit->id;
|
$this->request->invoice->cost_unit_id = $this->request->costUnit->id;
|
||||||
$this->request->invoice->type = $this->request->invoiceType;
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->request->invoice->comment = $this->request->comment;
|
$this->request->invoice->comment = $this->request->comment;
|
||||||
|
$this->request->invoice->changes = $changes;
|
||||||
|
|
||||||
$this->request->invoice->save();
|
$this->request->invoice->save();
|
||||||
|
|
||||||
@@ -29,5 +45,4 @@ class UpdateInvoiceCommand {
|
|||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ use App\ValueObjects\Amount;
|
|||||||
|
|
||||||
class UpdateInvoiceRequest {
|
class UpdateInvoiceRequest {
|
||||||
public string $comment;
|
public string $comment;
|
||||||
public string $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, string $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;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\UploadInvoice;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||||
|
use App\Providers\WebDavProvider;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class UploadInvoiceCommand {
|
||||||
|
private UploadInvoiceRequest $request;
|
||||||
|
public function __construct(UploadInvoiceRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UploadInvoiceResponse {
|
||||||
|
$uploadResponse = new UploadInvoiceResponse();
|
||||||
|
|
||||||
|
$uploadDir = sprintf(
|
||||||
|
'%1$s%2$s/%3$s',
|
||||||
|
WebDavProvider::INVOICE_PREFIX,
|
||||||
|
app('tenant')->url,
|
||||||
|
$this->request->invoice->costUnit()->first()->name
|
||||||
|
);
|
||||||
|
|
||||||
|
$webDavProvider = new WebDavProvider($uploadDir);
|
||||||
|
|
||||||
|
$createInvoiceReceiptRequest = new CreateInvoiceReceiptRequest($this->request->invoice);
|
||||||
|
$createInvoiceReceiptCommand = new CreateInvoiceReceiptCommand($createInvoiceReceiptRequest);
|
||||||
|
$response = $createInvoiceReceiptCommand->execute();
|
||||||
|
if ('' === $response->fileName) {
|
||||||
|
app('taskLogger')->error('PDF oder ZIP zur Abrechnung konnte nicht erstellt werden.');
|
||||||
|
return $uploadResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($webDavProvider->uploadFile($response->fileName)) {
|
||||||
|
$this->request->invoice->upload_required = false;
|
||||||
|
$this->request->invoice->save();
|
||||||
|
$uploadResponse->success = true;
|
||||||
|
} else {
|
||||||
|
app('taskLogger')->error('PDF oder ZIP zur Abrechnung konnte nicht hochgeladen werden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::exists($response->fileName)) {
|
||||||
|
Storage::delete($response->fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uploadResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\UploadInvoice;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
|
||||||
|
class UploadInvoiceRequest {
|
||||||
|
public Invoice $invoice;
|
||||||
|
|
||||||
|
public function __construct(Invoice $invoice) {
|
||||||
|
$this->invoice = $invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\UploadInvoice;
|
||||||
|
|
||||||
|
class UploadInvoiceResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceCommand;
|
|||||||
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceRequest;
|
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceRequest;
|
||||||
use App\Enumerations\CostUnitType;
|
use App\Enumerations\CostUnitType;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
use App\Resources\InvoiceResource;
|
use App\Resources\InvoiceResource;
|
||||||
use App\Scopes\CommonController;
|
use App\Scopes\CommonController;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
@@ -80,10 +81,13 @@ class EditController extends CommonController{
|
|||||||
$amountLeft->subtractAmount($newAmount);
|
$amountLeft->subtractAmount($newAmount);
|
||||||
|
|
||||||
$newCostUnit = $this->costUnits->getById($modifyData['cost_unit'],true);
|
$newCostUnit = $this->costUnits->getById($modifyData['cost_unit'],true);
|
||||||
|
$invoiceType = InvoiceType::where('slug', $modifyData['type_internal'])->first();
|
||||||
|
|
||||||
|
|
||||||
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
||||||
$invoice,
|
$invoice,
|
||||||
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
||||||
$modifyData['type_internal'],
|
$invoiceType,
|
||||||
$newCostUnit,
|
$newCostUnit,
|
||||||
$newAmount
|
$newAmount
|
||||||
);
|
);
|
||||||
|
|||||||
61
app/Domains/Invoice/Controllers/ListMyInvoicesController.php
Normal file
61
app/Domains/Invoice/Controllers/ListMyInvoicesController.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Controllers;
|
||||||
|
|
||||||
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class ListMyInvoicesController extends CommonController {
|
||||||
|
function __invoke(string $invoiceStatus) {
|
||||||
|
$invoices = $this->invoices->getMyInvoicesByStatus($invoiceStatus);
|
||||||
|
|
||||||
|
$subTabIndex = 0;
|
||||||
|
switch ($invoiceStatus) {
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_NEW:
|
||||||
|
$subTabIndex = 0;
|
||||||
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_APPROVED:
|
||||||
|
$subTabIndex = 1;
|
||||||
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_DENIED:
|
||||||
|
$subTabIndex = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$inertiaProvider = new InertiaProvider('Invoice/ListMyInvoices', [
|
||||||
|
'invoices' => $invoices,
|
||||||
|
'endpoint' => $invoiceStatus,
|
||||||
|
'currentStatus' => $subTabIndex,
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMyInvoicesByStatus(string $invoiceStatus) : JsonResponse {
|
||||||
|
$invoices = $this->invoices->getMyInvoicesByStatus($invoiceStatus);
|
||||||
|
|
||||||
|
$title = '';
|
||||||
|
switch ($invoiceStatus) {
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_NEW:
|
||||||
|
$title = 'Neue Abrechnungen';
|
||||||
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_APPROVED:
|
||||||
|
$title = 'Freigegebene Abrechnungen, nicht exportierte Abrechnungen';
|
||||||
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_DENIED:
|
||||||
|
$title = 'Abgelehnte Abrechnungen';
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'title' => $title,
|
||||||
|
'endpoint' => $invoiceStatus,
|
||||||
|
'invoices' => $invoices,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
|||||||
use App\Domains\CostUnit\Controllers\ListController;
|
use App\Domains\CostUnit\Controllers\ListController;
|
||||||
use App\Domains\Invoice\Controllers\ChangeStateController;
|
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\NewInvoiceController;
|
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
||||||
use App\Domains\Invoice\Controllers\ShowInvoiceController;
|
use App\Domains\Invoice\Controllers\ShowInvoiceController;
|
||||||
use App\Middleware\IdentifyTenant;
|
use App\Middleware\IdentifyTenant;
|
||||||
@@ -19,6 +20,7 @@ Route::middleware(IdentifyTenant::class)->group(function () {
|
|||||||
Route::post('/details/{invoiceId}/change-state/{newState}', ChangeStateController::class);
|
Route::post('/details/{invoiceId}/change-state/{newState}', ChangeStateController::class);
|
||||||
Route::post('/details/{invoiceId}/copy', [EditController::class, 'copyInvoice']);
|
Route::post('/details/{invoiceId}/copy', [EditController::class, 'copyInvoice']);
|
||||||
Route::post('/details/{invoiceId}/update', [EditController::class, 'updateInvoice']);
|
Route::post('/details/{invoiceId}/update', [EditController::class, 'updateInvoice']);
|
||||||
|
Route::get('/my-invoices/{invoiceStatus}', [ListMyInvoicesController::class, 'getMyInvoicesByStatus']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use App\Domains\CostUnit\Controllers\OpenController;
|
||||||
use App\Domains\CostUnit\Controllers\CreateController;
|
use App\Domains\Invoice\Controllers\ListMyInvoicesController;
|
||||||
use App\Domains\CostUnit\Controllers\ListController;
|
|
||||||
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
||||||
use App\Middleware\IdentifyTenant;
|
use App\Middleware\IdentifyTenant;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -10,10 +9,9 @@ Route::middleware(IdentifyTenant::class)->group(function () {
|
|||||||
Route::prefix('invoice')->group(function () {
|
Route::prefix('invoice')->group(function () {
|
||||||
Route::get('/new', NewInvoiceController::class);
|
Route::get('/new', NewInvoiceController::class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
Route::get('/create', [CreateController::class, 'showForm']);
|
Route::get('/my-invoices/{invoiceStatus}', ListMyInvoicesController::class);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
58
app/Domains/Invoice/Views/ListMyInvoices.vue
Normal file
58
app/Domains/Invoice/Views/ListMyInvoices.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<script setup>
|
||||||
|
import {reactive, inject, onMounted} from 'vue';
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||||
|
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import ListInvoices from "./Partials/myInvoiceDetails/ListInvoices.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
currentStatus: Number,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(props.currentStatus)
|
||||||
|
const initialCostUnitId = props.cost_unit_id
|
||||||
|
const initialInvoiceId = props.invoice_id
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: 'Neue Abrechnungen',
|
||||||
|
component: ListInvoices,
|
||||||
|
endpoint: "/api/v1/invoice/my-invoices/new",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Freigegebene Abrechnungen',
|
||||||
|
component: ListInvoices,
|
||||||
|
endpoint: "/api/v1/invoice/my-invoices/approved",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Abgelehnte Abrechnungen',
|
||||||
|
component: ListInvoices,
|
||||||
|
endpoint: "/api/v1/invoice/my-invoices/denied",
|
||||||
|
deep_jump_id: initialCostUnitId,
|
||||||
|
deep_jump_id_sub: initialInvoiceId,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.message) {
|
||||||
|
toast.success(props.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title="Meine Abrechnungen">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<tabbed-page :tabs="tabs" :subTabIndex=props.currentStatus />
|
||||||
|
|
||||||
|
</shadowed-box>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
@@ -25,37 +25,40 @@ const emit = defineEmits(["accept", "deny", "fix", "reopen"])
|
|||||||
<td v-else style="width: 300px;">Kostensatelle (ursprünglich)</td>
|
<td v-else style="width: 300px;">Kostensatelle (ursprünglich)</td>
|
||||||
|
|
||||||
<td>{{props.data.costUnitName}}</td>
|
<td>{{props.data.costUnitName}}</td>
|
||||||
<td rowspan="4">
|
<td rowspan="4" style="width: 250px; padding-right: 25px; text-align: right; vertical-align: top;">
|
||||||
<button
|
<input type="button"
|
||||||
v-if="props.data.status === 'new' && modeShow"
|
v-if="props.data.status === 'new' && modeShow"
|
||||||
@click="emit('accept')"
|
@click="emit('accept')"
|
||||||
class="button mareike-button mareike-accept-button"
|
class="accept-button"
|
||||||
>
|
style="width: 100%; margin-bottom: 10px;"
|
||||||
Abrechnung annehmen
|
value="Abrechnung annehmen"
|
||||||
</button>
|
/>
|
||||||
<button v-if="props.data.status === 'denied' && modeShow"
|
|
||||||
|
<input type="button" v-if="props.data.status === 'denied' && modeShow"
|
||||||
@click="emit('reopen')"
|
@click="emit('reopen')"
|
||||||
class="button mareike-button mareike-accept-button"
|
class="accept-button"
|
||||||
>
|
style="width: 100%; margin-bottom: 10px;"
|
||||||
Abrechnung zur Wiedervorlage öffnen
|
value="Abrechnung zur Wiedervorlage öffnen"
|
||||||
</button>
|
/>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<button
|
<input type="button"
|
||||||
v-if="props.data.status === 'new' && modeShow"
|
v-if="props.data.status === 'new' && modeShow"
|
||||||
@click="emit('fix')"
|
@click="emit('fix')"
|
||||||
class="button mareike-button mareike-fix-button"
|
class="fix-button"
|
||||||
>
|
style="width: 100%; margin-bottom: 10px;"
|
||||||
Abrechnung ablehnen und korrigieren
|
value="Abrechnung ablehnen und korrigieren"
|
||||||
</button><br />
|
/>
|
||||||
|
<br />
|
||||||
|
|
||||||
<button
|
<input type="button"
|
||||||
v-if="props.data.status === 'new' && modeShow"
|
v-if="props.data.status === 'new' && modeShow"
|
||||||
@click="emit('deny')"
|
@click="emit('deny')"
|
||||||
class="button mareike-button mareike-deny-button"
|
class="deny-button"
|
||||||
>
|
style="width: 100%"
|
||||||
Abrechnung ablehnen
|
value="Abrechnung ablehnen"
|
||||||
</button>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
invoice: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<table class="travel_allowance">
|
||||||
|
<tr><td colspan="2">
|
||||||
|
Abrechnung einer Reisekostenpauschale
|
||||||
|
</td></tr>
|
||||||
|
<tr>
|
||||||
|
<th>Reiseroute</th>
|
||||||
|
<td>{{props.invoice.travelRoute}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Gesamte Wegstrecke</th>
|
||||||
|
<td>{{props.invoice.distance}} km</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Kilometerpauschale</th>
|
||||||
|
<td>{{props.invoice.distanceAllowance}} Euro / km</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Gesamtbetrag</th>
|
||||||
|
<td style="font-weight: bold">{{props.invoice.amount}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Marterialtransport</th>
|
||||||
|
<td>{{props.invoice.transportation}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Hat Personen mitgenommen</th>
|
||||||
|
<td>{{props.invoice.passengers}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.travel_allowance {
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel_allowance tr th {
|
||||||
|
width: 300px !important;
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel_allowance tr td,
|
||||||
|
.travel_allowance tr th {
|
||||||
|
font-family: sans-serif;
|
||||||
|
line-height: 1.8em;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel_allowance tr td:last-child {
|
||||||
|
border-right: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel_allowance tr:first-child td:first-child {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
background: linear-gradient(to bottom, #fff, #f6f7f7);
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
border-left: 1px solid #ccc !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
104
app/Domains/Invoice/Views/Partials/myInvoiceDetails/Header.vue
Normal file
104
app/Domains/Invoice/Views/Partials/myInvoiceDetails/Header.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
modeShow: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(["accept", "deny", "fix", "reopen"])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span id="invoice_details_header">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Name:</td>
|
||||||
|
<td v-if="modeShow">{{props.data.contactName}}</td>
|
||||||
|
<td v-else style="width: 300px;">{{props.data.contactName}}</td>
|
||||||
|
|
||||||
|
<td v-if="modeShow" style="width: 250px;">Kostenstelle</td>
|
||||||
|
<td v-else style="width: 300px;">Kostensatelle (ursprünglich)</td>
|
||||||
|
|
||||||
|
<td>{{props.data.costUnitName}}</td>
|
||||||
|
<td
|
||||||
|
rowspan="4"
|
||||||
|
style="vertical-align: top;
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-right: 25px;
|
||||||
|
text-align: right;"
|
||||||
|
>
|
||||||
|
<input type="button"
|
||||||
|
v-if="props.data.status === 'new' && modeShow"
|
||||||
|
@click="emit('reject')"
|
||||||
|
class="deny-button"
|
||||||
|
value="Abrechnung zurückziehen" />
|
||||||
|
|
||||||
|
<input type="button"
|
||||||
|
v-if="props.data.status === 'denied' && modeShow"
|
||||||
|
@click="emit('delete')"
|
||||||
|
class="deny-button"
|
||||||
|
value="Abrechnung Endgültig löschen" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Rest der Tabelle bleibt unverändert -->
|
||||||
|
<tr>
|
||||||
|
<td>E-Mail:</td>
|
||||||
|
<td>{{props.data.contactEmail}}</td>
|
||||||
|
<td>
|
||||||
|
Abrechnungsnummer
|
||||||
|
<label v-if="!modeShow"> (ursprünglich)</label>:
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{props.data.invoiceNumber}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Telefon:</td>
|
||||||
|
<td>{{props.data.contactPhone}}</td>
|
||||||
|
<td>
|
||||||
|
Abrechnungstyp
|
||||||
|
<label v-if="!modeShow"> (Ursprünglich)</label>:
|
||||||
|
</td>
|
||||||
|
<td>{{props.data.invoiceType}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Kontoinhaber*in:</td>
|
||||||
|
<td>{{props.data.accountOwner}}</td>
|
||||||
|
<td>Gesamtbetrag
|
||||||
|
<label v-if="!modeShow"> (Ursprünglich)</label>:
|
||||||
|
</td>
|
||||||
|
<td><strong>{{props.data.amount}}</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>IBAN:</td>
|
||||||
|
<td>{{props.data.accountIban}}</td>
|
||||||
|
<td>Buchungsinformationen:</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>Klassische Auszahlung</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status:</td>
|
||||||
|
<td>{{props.data.readableStatus}}</td>
|
||||||
|
<td>Anmerkungen:</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="props.data.status === 'denied'">
|
||||||
|
{{props.data.deniedReason}}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{props.data.comment}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import FullScreenModal from "../../../../../Views/Components/FullScreenModal.vue";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import Modal from "../../../../../Views/Components/Modal.vue";
|
||||||
|
import { useAjax } from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ShowInvoicePartial from "./ShowInvoice.vue";
|
||||||
|
import Header from "./Header.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}, showInvoice: Boolean
|
||||||
|
})
|
||||||
|
const showInvoice = ref(props.showInvoice)
|
||||||
|
const emit = defineEmits(["close"])
|
||||||
|
const denyInvoiceDialog = ref(false)
|
||||||
|
const { data, loading, error, request } = useAjax()
|
||||||
|
const modeShow = ref(true)
|
||||||
|
const costUnits = ref(null)
|
||||||
|
const newInvoice = ref(null)
|
||||||
|
|
||||||
|
async function acceptInvoice() {
|
||||||
|
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/approved", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
toast.success('Abrechnung wurde freigegeben.');
|
||||||
|
} else {
|
||||||
|
toast.error('Bei der Bearbeitung ist ein Fehler aufgetreten.');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
emit('reload')
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateInvoice(formData) {
|
||||||
|
const data = await request("/api/v1/invoice/details/" + props.data.id + "/update", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
invoiceData: formData
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data.do_copy) {
|
||||||
|
modeShow.value = true;
|
||||||
|
toast.success('Die Koreektur der Abrechnung wurde gespeichert.');
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
modeShow.value = true;
|
||||||
|
newInvoice.value = data.invoice;
|
||||||
|
props.data.id = data.invoice.id;
|
||||||
|
reloadInvoiceFixDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadInvoiceFixDialog() {
|
||||||
|
const data = await request("api/v1/invoice/details/" + props.data.id, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
newInvoice.value = data.invoice;
|
||||||
|
props.data.id = data.invoice.id;
|
||||||
|
costUnits.value = data.costUnits;
|
||||||
|
props.data.id = data.invoice.id;
|
||||||
|
|
||||||
|
modeShow.value = false;
|
||||||
|
toast.success('Die Abrechnung wurde gespeichert und eine neue Abrechnung wurde erstellt.');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function openInvoiceFixDialog() {
|
||||||
|
const data = await request("/api/v1/invoice/details/" + props.data.id + "/copy", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
costUnits.value = data.costUnits;
|
||||||
|
newInvoice.value = data.invoice;
|
||||||
|
props.data.id = data.invoice.id;
|
||||||
|
modeShow.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function openDenyInvoiceDialog() {
|
||||||
|
denyInvoiceDialog.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rejectInvoice() {
|
||||||
|
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/denied", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
reason: 'Von Antragssteller*in zurückgezogen'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
toast.success('Abrechnung wurde zurückgezogen.');
|
||||||
|
} else {
|
||||||
|
toast.error('Bei der Bearbeitung ist ein Fehler aufgetreten.');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
denyInvoiceDialog.value = false;
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteInvoice() {
|
||||||
|
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/deleted", {
|
||||||
|
method: "POST",
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
toast.success('Die Abrechnung wurde gelöscht.');
|
||||||
|
} else {
|
||||||
|
toast.error('Beim Bearbeiten ist ein Fehler aufgetreten.');
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<FullScreenModal
|
||||||
|
:show="showInvoice"
|
||||||
|
title="Abrechnungsdetails"
|
||||||
|
@close="emit('close')"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Header :data="props.data"
|
||||||
|
@reject="rejectInvoice"
|
||||||
|
@delete="deleteInvoice"
|
||||||
|
:modeShow="modeShow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ShowInvoicePartial :data="props.data" />
|
||||||
|
|
||||||
|
</FullScreenModal>
|
||||||
|
|
||||||
|
<Modal title="Abrechnung ablehnen" :show="denyInvoiceDialog" @close="denyInvoiceDialog = false" >
|
||||||
|
Begründung:
|
||||||
|
<textarea class="mareike-textarea" style="width: 100%; height: 100px; margin-top: 10px;" id="deny_invoice_reason" />
|
||||||
|
<input type="button" class="mareike-button mareike-deny-invoice-button" value="Abrechnung ablehnen" @click="denyInvoice" />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.mareike-deny-invoice-button {
|
||||||
|
width: 150px !important;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_header{
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12pt;
|
||||||
|
line-height: 1.8em;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
#invoice_details_header table {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
width: 98%;
|
||||||
|
border-color: #c0c0c0;
|
||||||
|
box-shadow: 5px 5px 10px #c0c0c0;
|
||||||
|
margin-bottom: 75px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
#invoice_details_header table tr td:first-child {
|
||||||
|
padding-right: 50px;
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_header table tr td:nth-child(2) {
|
||||||
|
padding-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_header table tr td:nth-child(3) {
|
||||||
|
padding-right: 50px;
|
||||||
|
width: 100px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_body {
|
||||||
|
height: 400px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#invoice_details_body table tr:nth-child(1) td,
|
||||||
|
#invoice_details_body table tr:nth-child(2) td,
|
||||||
|
#invoice_details_body table tr:nth-child(3) td,
|
||||||
|
#invoice_details_body table tr:nth-child(4) td,
|
||||||
|
#invoice_details_body table tr:nth-child(6) td{
|
||||||
|
vertical-align: top;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_body table tr:nth-child(5) td {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#invoice_details_body table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_body table tr:nth-child(1) td:first-child {
|
||||||
|
padding-right: 50px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#invoice_details_body table tr:nth-child(1) td:nth-child(2),
|
||||||
|
#invoice_details_body table tr:nth-child(1) td:nth-child(3)
|
||||||
|
{
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mareike-accept-button {
|
||||||
|
background-color: #36c054 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mareike-deny-button {
|
||||||
|
background-color: #ee4b5c !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mareike-fix-button {
|
||||||
|
background-color: #d3d669 !important;
|
||||||
|
color: #67683c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mareike-button {
|
||||||
|
padding: 5px 25px !important;
|
||||||
|
font-size: 11pt !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<script setup>
|
||||||
|
import Icon from "../../../../../Views/Components/Icon.vue";
|
||||||
|
import InvoiceDetails from "./InvoiceDetails.vue";
|
||||||
|
import { useAjax } from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
const invoice = ref(null)
|
||||||
|
const show_invoice = ref(false)
|
||||||
|
const localData = ref(props.data)
|
||||||
|
|
||||||
|
async function openInvoiceDetails(invoiceId) {
|
||||||
|
const url = '/api/v1/invoice/details/' + invoiceId
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { method: 'GET' })
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
invoice.value = result.invoice
|
||||||
|
show_invoice.value = true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching invoices:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reload() {
|
||||||
|
const url = "/api/v1/invoice/my-invoices/" + props.data.endpoint
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { method: 'GET' })
|
||||||
|
if (!response.ok) throw new Error('Fehler beim Laden')
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
localData.value = result
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching invoices:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<table v-if="localData.invoices.length > 0" class="invoice-list-table">
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">{{props.data.title}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="invoice in localData.invoices" :id="'invoice_' + invoice.id">
|
||||||
|
<td>{{invoice.invoiceNumber}}</td>
|
||||||
|
<td>{{invoice.invoiceType}}</td>
|
||||||
|
<td>
|
||||||
|
{{invoice.amount}}
|
||||||
|
</td>
|
||||||
|
<td style="width: 150px;">
|
||||||
|
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
||||||
|
<Icon v-if="invoice.alreadyPaid" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{invoice.contactName}}<br />
|
||||||
|
<label v-if="invoice.contactEmail !== '--'">{{invoice.contactEmail}}<br /></label>
|
||||||
|
<label v-if="invoice.contactPhone !== '--'">{{invoice.contactPhone}}<br /></label>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<input type="button" value="Abrechnung Anzeigen" @click="openInvoiceDetails(invoice.id)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p v-else>Es sind keine Abrechnungen in dieser Kategorie vorhanden.</p>
|
||||||
|
|
||||||
|
<InvoiceDetails :data="invoice" :show-invoice="show_invoice" v-if="show_invoice" @close="show_invoice = false; reload()" />
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup>
|
||||||
|
import PdfViewer from "../../../../../Views/Components/PdfViewer.vue";
|
||||||
|
import DistanceAllowance from "./DistanceAllowance.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span id="invoice_details_body">
|
||||||
|
<PdfViewer :url="'/api/v1/invoice/showReceipt/' + props.data.id" v-if="props.data.documentFilename !== null" />
|
||||||
|
<DistanceAllowance v-else :invoice="props.data" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
11
app/Enumerations/CronTaskType.php
Normal file
11
app/Enumerations/CronTaskType.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class CronTaskType extends CommonModel{
|
||||||
|
public const CRON_TASK_TYPE_REALTIME = 'realtime';
|
||||||
|
public const CRON_TASK_TYPE_DAILY = 'daily';
|
||||||
|
}
|
||||||
|
|
||||||
27
app/Enumerations/EfzStatus.php
Normal file
27
app/Enumerations/EfzStatus.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $name
|
||||||
|
*/
|
||||||
|
class EfzStatus extends CommonModel
|
||||||
|
{
|
||||||
|
public const EFZ_STATUS_NOT_CHECKED = 'not_checked';
|
||||||
|
public const EFZ_STATUS_NOT_REQUIRED = 'not_required';
|
||||||
|
|
||||||
|
public const EFZ_STATUS_CHECKED_VALID = 'checked_valid';
|
||||||
|
public const EFZ_STATUS_CHECKED_INVALID = 'checked_invalid';
|
||||||
|
|
||||||
|
protected $table = 'efz_status';
|
||||||
|
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
}
|
||||||
15
app/Enumerations/ParticipationFeeType.php
Normal file
15
app/Enumerations/ParticipationFeeType.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class ParticipationFeeType extends CommonModel {
|
||||||
|
public const PARTICIPATION_FEE_TYPE_FIXED = 'fixed';
|
||||||
|
public const PARTICIPATION_FEE_TYPE_SOLIDARITY = 'solidarity';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
}
|
||||||
18
app/Enumerations/ParticipationType.php
Normal file
18
app/Enumerations/ParticipationType.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class ParticipationType extends CommonModel {
|
||||||
|
public const PARTICIPATION_TYPE_PARTICIPANT = 'participant';
|
||||||
|
public const PARTICIPATION_TYPE_TEAM = 'team';
|
||||||
|
public const PARTICIPATION_TYPE_VOLUNTEER = 'volunteer';
|
||||||
|
public const PARTICIPATION_TYPE_OTHER = 'other';
|
||||||
|
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -19,7 +19,9 @@ class DevelopmentDataSeeder {
|
|||||||
'slug' => 'wilde-moehre',
|
'slug' => 'wilde-moehre',
|
||||||
'name' => 'Stamm Wilde Möhre',
|
'name' => 'Stamm Wilde Möhre',
|
||||||
'url' => 'wilde-moehre.mareike.local',
|
'url' => 'wilde-moehre.mareike.local',
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Wilde Möhre',
|
||||||
'account_iban' => 'DE12345678901234567890',
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
'email' => 'test@example1.com',
|
'email' => 'test@example1.com',
|
||||||
'city' => 'Halle (Saale)',
|
'city' => 'Halle (Saale)',
|
||||||
'postcode' => '06120',
|
'postcode' => '06120',
|
||||||
|
|||||||
@@ -3,16 +3,22 @@
|
|||||||
namespace App\Installer;
|
namespace App\Installer;
|
||||||
|
|
||||||
use App\Enumerations\CostUnitType;
|
use App\Enumerations\CostUnitType;
|
||||||
|
use App\Enumerations\CronTaskType;
|
||||||
use App\Enumerations\EatingHabit;
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Enumerations\EfzStatus;
|
||||||
use App\Enumerations\FirstAidPermission;
|
use App\Enumerations\FirstAidPermission;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
use App\Enumerations\InvoiceType;
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use App\Enumerations\ParticipationType;
|
||||||
use App\Enumerations\SwimmingPermission;
|
use App\Enumerations\SwimmingPermission;
|
||||||
use App\Enumerations\UserRole;
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\CronTask;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
|
|
||||||
class ProductionDataSeeder {
|
class ProductionDataSeeder {
|
||||||
public function execute() {
|
public function execute() {
|
||||||
|
$this->installCronTypes();
|
||||||
$this->installUserRoles();
|
$this->installUserRoles();
|
||||||
$this->installCostUnitTypes();
|
$this->installCostUnitTypes();
|
||||||
$this->installSwimmingPermissions();
|
$this->installSwimmingPermissions();
|
||||||
@@ -20,7 +26,44 @@ class ProductionDataSeeder {
|
|||||||
$this->installFirstAidPermissions();
|
$this->installFirstAidPermissions();
|
||||||
$this->installTenants();
|
$this->installTenants();
|
||||||
$this->installInvoiceMetaData();
|
$this->installInvoiceMetaData();
|
||||||
|
$this->installParticipationFeeTypes();
|
||||||
|
$this->installParticipationTypes();
|
||||||
|
$this->installEfzStatus();
|
||||||
|
$this->installCronTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installEfzStatus() {
|
||||||
|
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']);
|
||||||
|
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']);
|
||||||
|
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_CHECKED_VALID, 'name' => 'Geprüft und gültig']);
|
||||||
|
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_CHECKED_INVALID, 'name' => 'Geprüft und ungültig']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installParticipationTypes() {
|
||||||
|
ParticipationType::create([
|
||||||
|
'slug' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||||
|
'name' => 'Teilnehmende'
|
||||||
|
]);
|
||||||
|
|
||||||
|
ParticipationType::create([
|
||||||
|
'slug' => ParticipationType::PARTICIPATION_TYPE_TEAM,
|
||||||
|
'name' => 'Kernteam'
|
||||||
|
]);
|
||||||
|
|
||||||
|
ParticipationType::create([
|
||||||
|
'slug' => ParticipationType::PARTICIPATION_TYPE_VOLUNTEER,
|
||||||
|
'name' => 'Unterstützende'
|
||||||
|
]);
|
||||||
|
|
||||||
|
ParticipationType::create([
|
||||||
|
'slug' => ParticipationType::PARTICIPATION_TYPE_OTHER,
|
||||||
|
'name' => 'Sonstige'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installParticipationFeeTypes() {
|
||||||
|
ParticipationFeeType::create(['slug' => ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY, 'name' => 'Solidaritätsprinzip']);
|
||||||
|
ParticipationFeeType::create(['slug' => ParticipationFeeType::PARTICIPATION_FEE_TYPE_FIXED, 'name' => 'Festpreis']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function installUserRoles() {
|
private function installUserRoles() {
|
||||||
@@ -64,7 +107,9 @@ class ProductionDataSeeder {
|
|||||||
'slug' => 'lv',
|
'slug' => 'lv',
|
||||||
'name' => 'Landesunmittelbare Mitglieder',
|
'name' => 'Landesunmittelbare Mitglieder',
|
||||||
'url' => 'mareike.local',
|
'url' => 'mareike.local',
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V.',
|
||||||
'account_iban' => 'DE12345678901234567890',
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
'email' => 'test@example.com',
|
'email' => 'test@example.com',
|
||||||
'city' => 'Lommatzsch',
|
'city' => 'Lommatzsch',
|
||||||
'postcode' => '01623',
|
'postcode' => '01623',
|
||||||
@@ -103,6 +148,16 @@ class ProductionDataSeeder {
|
|||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_APPROVED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_APPROVED]);
|
||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_EXPORTED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_EXPORTED]);
|
||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_DENIED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_DENIED]);
|
||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STAUTS_DELETED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_DELETED]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installCronTypes() {
|
||||||
|
CronTaskType::create(['slug' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
||||||
|
CronTaskType::create(['slug' => CronTaskType::CRON_TASK_TYPE_DAILY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installCronTasks() {
|
||||||
|
CronTask::create(['name' => 'UploadInvoices', 'execution_type' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
app/Models/CronTask.php
Normal file
12
app/Models/CronTask.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class CronTask extends CommonModel
|
||||||
|
{
|
||||||
|
protected $table = 'cron_tasks';
|
||||||
|
protected $fillable = ['name', 'execution_type', 'schedule_time', 'last_run'];
|
||||||
|
protected $dates = ['last_run'];
|
||||||
|
}
|
||||||
172
app/Models/Event.php
Normal file
172
app/Models/Event.php
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Casts\AmountCast;
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use App\RelationModels\EventParticipationFee;
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
use App\Scopes\InstancedModel;
|
||||||
|
use DateTime;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $tenant
|
||||||
|
* @property int $cost_unit_id
|
||||||
|
* @property string $name
|
||||||
|
* @property string $location
|
||||||
|
* @property string $postal_code
|
||||||
|
* @property string $email
|
||||||
|
* @property DateTime $start_date
|
||||||
|
* @property DateTime $end_date
|
||||||
|
* @property DateTime $early_bird_end
|
||||||
|
* @property DateTime $registration_final_end
|
||||||
|
* @property int $early_bird_end_amount_increase
|
||||||
|
* @property int $registration_final_end_amount_increase
|
||||||
|
* @property string $account_owner
|
||||||
|
* @property string $account_iban
|
||||||
|
* @property boolean $registration_allowed
|
||||||
|
* @property string $participation_fee_type
|
||||||
|
* @property float $total_max_amount
|
||||||
|
* @property string $registration_link
|
||||||
|
* @property boolean $pay_per_day
|
||||||
|
* @property boolean $pay_direct
|
||||||
|
* @property boolean $send_weekly_report
|
||||||
|
* @property int $participation_fee_1
|
||||||
|
* @property int $participation_fee_2
|
||||||
|
* @property int $participation_fee_3
|
||||||
|
* @property int $participation_fee_4
|
||||||
|
* @property float $support_per_person
|
||||||
|
* @property float $support_flat
|
||||||
|
* @property int $alcoholics_age
|
||||||
|
* @property boolean $archived
|
||||||
|
*/
|
||||||
|
class Event extends InstancedModel
|
||||||
|
{
|
||||||
|
protected $table = 'events';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant',
|
||||||
|
'cost_unit_id',
|
||||||
|
'name',
|
||||||
|
'location',
|
||||||
|
'postal_code',
|
||||||
|
'email',
|
||||||
|
'start_date',
|
||||||
|
'end_date',
|
||||||
|
'early_bird_end',
|
||||||
|
'registration_final_end',
|
||||||
|
'early_bird_end_amount_increase',
|
||||||
|
'registration_final_end_amount_increase',
|
||||||
|
'account_owner',
|
||||||
|
'account_iban',
|
||||||
|
'registration_allowed',
|
||||||
|
'participation_fee_type',
|
||||||
|
'total_max_amount',
|
||||||
|
'registration_link',
|
||||||
|
'pay_per_day',
|
||||||
|
'pay_direct',
|
||||||
|
'send_weekly_report',
|
||||||
|
'participation_fee_1',
|
||||||
|
'participation_fee_2',
|
||||||
|
'participation_fee_3',
|
||||||
|
'participation_fee_4',
|
||||||
|
'support_per_person',
|
||||||
|
'support_flat',
|
||||||
|
'alcoholics_age',
|
||||||
|
'archived',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'start_date' => 'datetime',
|
||||||
|
'end_date' => 'datetime',
|
||||||
|
'early_bird_end' => 'datetime',
|
||||||
|
'registration_final_end' => 'datetime',
|
||||||
|
|
||||||
|
'early_bird_end_amount_increase' => 'integer',
|
||||||
|
'registration_final_end_amount_increase' => 'integer',
|
||||||
|
'alcoholics_age' => 'integer',
|
||||||
|
|
||||||
|
'registration_allowed' => 'boolean',
|
||||||
|
'pay_per_day' => 'boolean',
|
||||||
|
'pay_direct' => 'boolean',
|
||||||
|
'send_weekly_report' => 'boolean',
|
||||||
|
'archived' => 'boolean',
|
||||||
|
|
||||||
|
'support_per_person' => AmountCast::class,
|
||||||
|
'support_flat' => AmountCast::class,
|
||||||
|
'total_max_amount' => AmountCast::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function tenant(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Tenant::class, 'tenant', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function costUnit(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CostUnit::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participationFeeType(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ParticipationFeeType::class, 'participation_fee_type', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participationFee1(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EventParticipationFee::class, 'participation_fee_1');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participationFee2(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EventParticipationFee::class, 'participation_fee_2');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participationFee3(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EventParticipationFee::class, 'participation_fee_3');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participationFee4(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EventParticipationFee::class, 'participation_fee_4');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function localGroups() : BelongsToMany{
|
||||||
|
return $this->belongsToMany(Tenant::class, 'event_local_groups', 'event_id', 'local_group_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eatingHabits() : BelongsToMany{
|
||||||
|
return $this->belongsToMany(EatingHabit::class, 'event_allowed_eating_habits', 'event_id', 'eating_habit_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetContributingLocalGroups() {
|
||||||
|
$this->localGroups()->detach();
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetAllowedEatingHabits() {
|
||||||
|
$this->eatingHabits()->detach();
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetMangers() {
|
||||||
|
$this->eventManagers()->detach();
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eventManagers() : BelongsToMany {
|
||||||
|
return $this->belongsToMany(User::class, 'event_managers', 'event_id', 'user_id')
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participants() : hasMany {
|
||||||
|
return $this->hasMany(EventParticipant::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
135
app/Models/EventParticipant.php
Normal file
135
app/Models/EventParticipant.php
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Casts\AmountCast;
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Enumerations\EfzStatus;
|
||||||
|
use App\Enumerations\FirstAidPermission;
|
||||||
|
use App\Enumerations\ParticipationType;
|
||||||
|
use App\Enumerations\SwimmingPermission;
|
||||||
|
use App\Scopes\InstancedModel;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
|
||||||
|
class EventParticipant extends InstancedModel
|
||||||
|
{
|
||||||
|
protected $table = 'event_participants';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant',
|
||||||
|
'event_id',
|
||||||
|
'user_id',
|
||||||
|
|
||||||
|
'firstname',
|
||||||
|
'lastname',
|
||||||
|
'nickname',
|
||||||
|
|
||||||
|
'participation_type',
|
||||||
|
'local_group',
|
||||||
|
'birthday',
|
||||||
|
|
||||||
|
'address_1',
|
||||||
|
'address_2',
|
||||||
|
'postcode',
|
||||||
|
'city',
|
||||||
|
|
||||||
|
'email_1',
|
||||||
|
'phone_1',
|
||||||
|
'email_2',
|
||||||
|
'phone_2',
|
||||||
|
'contact_person',
|
||||||
|
|
||||||
|
'allergies',
|
||||||
|
'intolerances',
|
||||||
|
'eating_habits',
|
||||||
|
'swimming_permission',
|
||||||
|
'first_aid_permission',
|
||||||
|
|
||||||
|
'foto_socialmedia',
|
||||||
|
'foto_print',
|
||||||
|
'foto_webseite',
|
||||||
|
'foto_partner',
|
||||||
|
'foto_intern',
|
||||||
|
|
||||||
|
'arrival_date',
|
||||||
|
'departure_date',
|
||||||
|
'arrival_eating',
|
||||||
|
'departure_eating',
|
||||||
|
|
||||||
|
'notes',
|
||||||
|
'amount',
|
||||||
|
'amount_paid',
|
||||||
|
'efz_status',
|
||||||
|
'unregistered_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'birthday' => 'datetime',
|
||||||
|
'arrival_date' => 'datetime',
|
||||||
|
'departure_date' => 'datetime',
|
||||||
|
'unregistered_at' => 'datetime',
|
||||||
|
|
||||||
|
'foto_socialmedia' => 'boolean',
|
||||||
|
'foto_print' => 'boolean',
|
||||||
|
'foto_webseite' => 'boolean',
|
||||||
|
'foto_partner' => 'boolean',
|
||||||
|
'foto_intern' => 'boolean',
|
||||||
|
|
||||||
|
'amount' => AmountCast::class,
|
||||||
|
'amount_paid' => AmountCast::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Relationships
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function event()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Event::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tenantRelation()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Tenant::class, 'tenant', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function localGroup()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Tenant::class, 'local_group', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function participationType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ParticipationType::class, 'participation_type', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function swimmingPermission()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SwimmingPermission::class, 'swimming_permission', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eatingHabit()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EatingHabit::class, 'eating_habits', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firstAidPermission()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(FirstAidPermission::class, 'first_aid_permission', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function efzStatus()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EfzStatus::class, 'efz_status', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -80,4 +80,8 @@ class Invoice extends InstancedModel
|
|||||||
public function invoiceType() : InvoiceType {
|
public function invoiceType() : InvoiceType {
|
||||||
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function approvedBy() : ?User {
|
||||||
|
return $this->belongsTo(User::class, 'approved_by')->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ use App\Scopes\CommonModel;
|
|||||||
* @property string $local_group
|
* @property string $local_group
|
||||||
* @property string $email
|
* @property string $email
|
||||||
* @property string $url
|
* @property string $url
|
||||||
|
* @property string $account_name
|
||||||
* @property string $account_iban
|
* @property string $account_iban
|
||||||
|
* @property string $account_bic
|
||||||
* @property string $city
|
* @property string $city
|
||||||
* @property string $postcode
|
* @property string $postcode
|
||||||
|
* @property boolean $download_exports
|
||||||
|
* @property boolean $upload_exports
|
||||||
* @property string $gdpr_text
|
* @property string $gdpr_text
|
||||||
* @property string $impress_text
|
* @property string $impress_text
|
||||||
* @property string $url_participation_rules
|
* @property string $url_participation_rules
|
||||||
@@ -20,6 +24,9 @@ use App\Scopes\CommonModel;
|
|||||||
*/
|
*/
|
||||||
class Tenant extends CommonModel
|
class Tenant extends CommonModel
|
||||||
{
|
{
|
||||||
|
public static function getTempDirectory() : string {
|
||||||
|
return app('tenant')->slug . '/temp-data/';
|
||||||
|
}
|
||||||
public const PRIMARY_TENANT_NAME = 'LV';
|
public const PRIMARY_TENANT_NAME = 'LV';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -27,9 +34,13 @@ class Tenant extends CommonModel
|
|||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'url',
|
'url',
|
||||||
|
'account_name',
|
||||||
'account_iban',
|
'account_iban',
|
||||||
|
'account_bic',
|
||||||
'city',
|
'city',
|
||||||
'postcode',
|
'postcode',
|
||||||
|
'download_exports',
|
||||||
|
'upload_exports',
|
||||||
'gdpr_text',
|
'gdpr_text',
|
||||||
'impress_text',
|
'impress_text',
|
||||||
'url_participation_rules',
|
'url_participation_rules',
|
||||||
|
|||||||
88
app/Providers/CronTaskHandleProvider.php
Normal file
88
app/Providers/CronTaskHandleProvider.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Enumerations\CronTaskType;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Models\CronTask;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class CronTaskHandleProvider extends CommonController
|
||||||
|
{
|
||||||
|
public function run(Request $request)
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
|
||||||
|
$tenants = Tenant::where('has_active_instance', true)->get();
|
||||||
|
|
||||||
|
foreach ($tenants as $tenant) {
|
||||||
|
app()->instance('tenant', $tenant);
|
||||||
|
$this->runTenantTasks($tenant, $now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'ok',
|
||||||
|
'time' => $now->toDateTimeString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runTenantTasks(Tenant $tenant, Carbon $now) {
|
||||||
|
$tasks = CronTask::all();
|
||||||
|
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
|
||||||
|
// --- Every-Time Tasks ---
|
||||||
|
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_REALTIME) {
|
||||||
|
$this->runTask($task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Daily Tasks ---
|
||||||
|
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_DAILY) {
|
||||||
|
$scheduledTime = $task->schedule_time;
|
||||||
|
$alreadyRunToday = $task->last_run?->isToday() ?? false;
|
||||||
|
|
||||||
|
if (!$alreadyRunToday && $now->format('H:i') === $scheduledTime) {
|
||||||
|
$this->runTask($task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runTask(CronTask $task)
|
||||||
|
{
|
||||||
|
$logger = $this->taskLogger($task->name, app('tenant'));
|
||||||
|
app()->instance('taskLogger', $logger);
|
||||||
|
|
||||||
|
|
||||||
|
$taskClass = "\\App\\Tasks\\" . $task->name;
|
||||||
|
if (class_exists($taskClass)) {
|
||||||
|
$instance = new $taskClass();
|
||||||
|
$instance->handle();
|
||||||
|
|
||||||
|
// Update last_run
|
||||||
|
$task->last_run = now();
|
||||||
|
$task->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function taskLogger(string $taskName, $tenant = null) : LoggerInterface
|
||||||
|
{
|
||||||
|
$tenantSlug = $tenant->slug;
|
||||||
|
$logDir = storage_path("logs/{$tenantSlug}");
|
||||||
|
|
||||||
|
if (!file_exists($logDir)) {
|
||||||
|
mkdir($logDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$logPath = "{$logDir}/{$taskName}.log";
|
||||||
|
|
||||||
|
return \Illuminate\Support\Facades\Log::build([
|
||||||
|
'driver' => 'single',
|
||||||
|
'path' => $logPath,
|
||||||
|
'level' => 'debug',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Providers/FileWriteProvider.php
Normal file
19
app/Providers/FileWriteProvider.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class FileWriteProvider {
|
||||||
|
public string $fileName;
|
||||||
|
public string $fileContent;
|
||||||
|
|
||||||
|
public function __construct(string $fileName, string $fileContent) {
|
||||||
|
$this->fileName = $fileName;
|
||||||
|
$this->fileContent = $fileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeToFile() {
|
||||||
|
Storage::disk('local')->put($this->fileName, $this->fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
use App\Enumerations\InvoiceType;
|
use App\Enumerations\InvoiceType;
|
||||||
use App\Enumerations\UserRole;
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Repositories\EventRepository;
|
||||||
use App\Repositories\PageTextRepository;
|
use App\Repositories\PageTextRepository;
|
||||||
use App\Resources\UserResource;
|
use App\Resources\UserResource;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -98,6 +102,9 @@ class GlobalDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function generateNavbar() : array {
|
private function generateNavbar() : array {
|
||||||
|
$eventRepository = new EventRepository();
|
||||||
|
|
||||||
|
|
||||||
$navigation = [
|
$navigation = [
|
||||||
'personal' => [],
|
'personal' => [],
|
||||||
'common' => [],
|
'common' => [],
|
||||||
@@ -116,6 +123,14 @@ class GlobalDataProvider {
|
|||||||
) {
|
) {
|
||||||
$navigation['costunits'][] = ['url' => '/cost-unit/list', 'display' => 'Kostenstellen'];
|
$navigation['costunits'][] = ['url' => '/cost-unit/list', 'display' => 'Kostenstellen'];
|
||||||
$navigation['costunits'][] = ['url' => '/cost-unit/create', 'display' => 'Neue laufende Tätigkeit'];
|
$navigation['costunits'][] = ['url' => '/cost-unit/create', 'display' => 'Neue laufende Tätigkeit'];
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($eventRepository->listAll() as $event) {
|
||||||
|
$navigation['events'][] = ['url' => '/event/details/' . $event->id, 'display' => $event->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$navigation['events'][] = ['url' => '/event/new', 'display' => 'Neue Veranstaltung'];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,5 +156,13 @@ class GlobalDataProvider {
|
|||||||
return $activeUsers;
|
return $activeUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEventSettingData() : JsonResponse {
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'localGroups' => Tenant::where(['is_active_local_group' => true])->get(),
|
||||||
|
'eatingHabits' => EatingHabit::all(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
49
app/Providers/InvoiceCsvFileProvider.php
Normal file
49
app/Providers/InvoiceCsvFileProvider.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Resources\InvoiceResource;
|
||||||
|
|
||||||
|
class InvoiceCsvFileProvider {
|
||||||
|
|
||||||
|
/* @var Invoice[] */
|
||||||
|
private array $invoices;
|
||||||
|
|
||||||
|
public function __construct(array $invoices) {
|
||||||
|
$this->invoices = $invoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createCsvFileContent() : string
|
||||||
|
{
|
||||||
|
$csvArray[] = implode(
|
||||||
|
',', [
|
||||||
|
'"Rechnungsnummer"',
|
||||||
|
'"Zahlungsgrund"',
|
||||||
|
'"Name"',
|
||||||
|
'"IBAN"',
|
||||||
|
'"Kontoinhaber*in"',
|
||||||
|
'"Betrag"',
|
||||||
|
'"Spende"',
|
||||||
|
'"Beleg ohne Auszahlung"',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($this->invoices as $invoice) {
|
||||||
|
$invoiceReadable = new InvoiceResource($invoice)->toArray();
|
||||||
|
$csvLine = implode(',', [
|
||||||
|
'"' . $invoiceReadable['invoiceNumber'] . '"',
|
||||||
|
'"' . $invoiceReadable['invoiceType'] . '"',
|
||||||
|
'"' . $invoiceReadable['contactName'] . '"',
|
||||||
|
'"' . $invoiceReadable['accountIban'] . '"',
|
||||||
|
'"' . $invoiceReadable['accountOwner'] . '"',
|
||||||
|
'"' . $invoiceReadable['amountPlain'] . '"',
|
||||||
|
'"' . $invoiceReadable['donation'] . '"',
|
||||||
|
'"' . $invoiceReadable['alreadyPaid'] . '"'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$csvArray[] = $csvLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(PHP_EOL, $csvArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
app/Providers/PainFileProvider.php
Normal file
127
app/Providers/PainFileProvider.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use DOMDocument;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PainFileProvider {
|
||||||
|
public string $senderIban;
|
||||||
|
public string $senderName;
|
||||||
|
public string $senderBic;
|
||||||
|
|
||||||
|
/* @var Invoice[] */
|
||||||
|
public array $invoices;
|
||||||
|
|
||||||
|
public function __construct(string $senderIban, string $senderName, string $senderBic, array $invoices) {
|
||||||
|
$this->senderIban = $senderIban;
|
||||||
|
$this->senderName = $senderName;
|
||||||
|
$this->senderBic = $senderBic;
|
||||||
|
$this->invoices = $invoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPainFileContent() : string {
|
||||||
|
$doc = new DOMDocument('1.0', 'UTF-8');
|
||||||
|
$doc->formatOutput = true;
|
||||||
|
|
||||||
|
$senderIban = str_replace(' ', '', $this->senderIban);
|
||||||
|
$senderBic = str_replace(' ', '', $this->senderBic);
|
||||||
|
|
||||||
|
// Root-Element
|
||||||
|
$document = $doc->createElement('Document');
|
||||||
|
$document->setAttribute('xmlns', 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03');
|
||||||
|
$document->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
|
||||||
|
$doc->appendChild($document);
|
||||||
|
|
||||||
|
// CstmrCdtTrfInitn
|
||||||
|
$cstmr_cdt_trf_initn = $doc->createElement('CstmrCdtTrfInitn');
|
||||||
|
$document->appendChild($cstmr_cdt_trf_initn);
|
||||||
|
|
||||||
|
// GrpHdr
|
||||||
|
$grp_hdr = $doc->createElement('GrpHdr');
|
||||||
|
$cstmr_cdt_trf_initn->appendChild($grp_hdr);
|
||||||
|
|
||||||
|
$grp_hdr->appendChild($doc->createElement('MsgId', uniqid('MSG')));
|
||||||
|
$grp_hdr->appendChild($doc->createElement('CreDtTm', date('c')));
|
||||||
|
$grp_hdr->appendChild($doc->createElement('NbOfTxs', count($this->invoices)));
|
||||||
|
|
||||||
|
$totalAmount = array_sum(array_column($this->invoices, 'amount'));
|
||||||
|
$grp_hdr->appendChild($doc->createElement('CtrlSum', number_format($totalAmount, 2, '.', '')));
|
||||||
|
|
||||||
|
$initg_pty = $doc->createElement('InitgPty');
|
||||||
|
$initg_pty->appendChild($doc->createElement('Nm', $this->senderName));
|
||||||
|
$grp_hdr->appendChild($initg_pty);
|
||||||
|
|
||||||
|
// PmtInf
|
||||||
|
$pmt_inf = $doc->createElement('PmtInf');
|
||||||
|
$cstmr_cdt_trf_initn->appendChild($pmt_inf);
|
||||||
|
|
||||||
|
$pmt_inf->appendChild($doc->createElement('PmtInfId', uniqid('PMT')));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('PmtMtd', 'TRF'));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('BtchBookg', 'false'));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('NbOfTxs', count($this->invoices)));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('CtrlSum', number_format($totalAmount, 2, '.', '')));
|
||||||
|
|
||||||
|
$pmt_tp_inf = $doc->createElement('PmtTpInf');
|
||||||
|
$svc_lvl = $doc->createElement('SvcLvl');
|
||||||
|
$svc_lvl->appendChild($doc->createElement('Cd', 'SEPA'));
|
||||||
|
$pmt_tp_inf->appendChild($svc_lvl);
|
||||||
|
$pmt_inf->appendChild($pmt_tp_inf);
|
||||||
|
|
||||||
|
$pmt_inf->appendChild($doc->createElement('ReqdExctnDt', date('Y-m-d', mktime(0))));
|
||||||
|
|
||||||
|
$dbtr = $doc->createElement('Dbtr');
|
||||||
|
$dbtr->appendChild($doc->createElement('Nm', $this->senderName));
|
||||||
|
$pmt_inf->appendChild($dbtr);
|
||||||
|
|
||||||
|
$dbtr_acct = $doc->createElement('DbtrAcct');
|
||||||
|
$id = $doc->createElement('Id');
|
||||||
|
$id->appendChild($doc->createElement('IBAN', $senderIban));
|
||||||
|
$dbtr_acct->appendChild($id);
|
||||||
|
$pmt_inf->appendChild($dbtr_acct);
|
||||||
|
|
||||||
|
|
||||||
|
$dbtr_agt = $doc->createElement('DbtrAgt');
|
||||||
|
$id = $doc->createElement('FinInstnId');
|
||||||
|
$id->appendChild($doc->createElement('BIC', $senderBic));
|
||||||
|
$dbtr_agt->appendChild($id);
|
||||||
|
$pmt_inf->appendChild($dbtr_agt);
|
||||||
|
|
||||||
|
foreach ($this->invoices as $index => $invoice) {
|
||||||
|
$cdt_trf_tx_inf = $doc->createElement('CdtTrfTxInf');
|
||||||
|
|
||||||
|
$pmt_id = $doc->createElement('PmtId');
|
||||||
|
$pmt_id->appendChild($doc->createElement('EndToEndId', uniqid('ET')));
|
||||||
|
$cdt_trf_tx_inf->appendChild($pmt_id);
|
||||||
|
|
||||||
|
$amt = $doc->createElement('Amt');
|
||||||
|
$instd_amt = $doc->createElement('InstdAmt', number_format($invoice['amount'], 2, '.', ''));
|
||||||
|
$instd_amt->setAttribute('Ccy', 'EUR');
|
||||||
|
$amt->appendChild($instd_amt);
|
||||||
|
$cdt_trf_tx_inf->appendChild($amt);
|
||||||
|
|
||||||
|
$cdtr = $doc->createElement('Cdtr');
|
||||||
|
$cdtr->appendChild($doc->createElement('Nm', $invoice['contact_bank_owner']));
|
||||||
|
$cdt_trf_tx_inf->appendChild($cdtr);
|
||||||
|
|
||||||
|
$cdtr_acct = $doc->createElement('CdtrAcct');
|
||||||
|
$cdtr_id = $doc->createElement('Id');
|
||||||
|
$cdtr_id->appendChild($doc->createElement('IBAN', str_replace(' ', '', $invoice['contact_bank_iban'])));
|
||||||
|
$cdtr_acct->appendChild($cdtr_id);
|
||||||
|
$cdt_trf_tx_inf->appendChild($cdtr_acct);
|
||||||
|
|
||||||
|
$rmt_inf = $doc->createElement('RmtInf');
|
||||||
|
$rmt_inf->appendChild($doc->createElement('Ustrd', 'Auslagenerstattung Rechnungsnummer ' . $invoice['invoice_number']));
|
||||||
|
$cdt_trf_tx_inf->appendChild($rmt_inf);
|
||||||
|
|
||||||
|
$pmt_inf->appendChild($cdt_trf_tx_inf);
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml_content = $doc->saveXML();
|
||||||
|
if (false === $xml_content) {
|
||||||
|
throw new Exception('Failed to generate XML content.');
|
||||||
|
}
|
||||||
|
return $xml_content;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/Providers/PdfMergeProvider.php
Normal file
46
app/Providers/PdfMergeProvider.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
use setasign\Fpdi\Fpdi;
|
||||||
|
|
||||||
|
class PdfMergeProvider {
|
||||||
|
protected array $files = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt eine PDF-Datei zur Merge-Liste hinzu.
|
||||||
|
*/
|
||||||
|
public function add(string $filePath): self
|
||||||
|
{
|
||||||
|
$this->files[] = $filePath;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Führt alle hinzugefügten PDFs zusammen und speichert sie.
|
||||||
|
*/
|
||||||
|
public function merge(string $outputPath): void
|
||||||
|
{
|
||||||
|
if (empty($this->files)) {
|
||||||
|
throw new RuntimeException("No PDF files added for merging.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf = new FPDI();
|
||||||
|
|
||||||
|
foreach ($this->files as $file) {
|
||||||
|
$pageCount = $pdf->setSourceFile($file);
|
||||||
|
|
||||||
|
for ($page = 1; $page <= $pageCount; $page++) {
|
||||||
|
$tpl = $pdf->importPage($page);
|
||||||
|
$size = $pdf->getTemplateSize($tpl);
|
||||||
|
|
||||||
|
$pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
|
||||||
|
$pdf->useTemplate($tpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->Output('F', $outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ class UploadFileProvider {
|
|||||||
|
|
||||||
$invoiceFile = new InvoiceFile();
|
$invoiceFile = new InvoiceFile();
|
||||||
$invoiceFile->filename = $filename;
|
$invoiceFile->filename = $filename;
|
||||||
$invoiceFile->path = $path;
|
$invoiceFile->fullPath = $path;
|
||||||
return $invoiceFile;
|
return $invoiceFile;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
27
app/Providers/WebDavProvider.php
Normal file
27
app/Providers/WebDavProvider.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
use WebDav\WebDav\WebDavClient;
|
||||||
|
class WebDavProvider {
|
||||||
|
public const INVOICE_PREFIX = 'Abrechnungen ';
|
||||||
|
|
||||||
|
private WebDavClient $webDavClient;
|
||||||
|
private string $workingDirectory = '/';
|
||||||
|
|
||||||
|
public function __construct($workingDirectory) {
|
||||||
|
$this->webDavClient = new WebDavClient(env('WEBDAV_HOST'), env('WEBDAV_USER'), env('WEBDAV_PASS'));
|
||||||
|
$this->workingDirectory = $workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadFile(string $fileName) : bool {
|
||||||
|
$baseDir = storage_path('app/private/');
|
||||||
|
|
||||||
|
return $this->webDavClient->upload_file($baseDir . $fileName, $this->workingDirectory . '/'.
|
||||||
|
basename($fileName)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
app/Providers/ZipArchiveFileProvider.php
Normal file
29
app/Providers/ZipArchiveFileProvider.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class ZipArchiveFileProvider {
|
||||||
|
private ZipArchive $zipArchive;
|
||||||
|
|
||||||
|
public function __construct(string $filename) {
|
||||||
|
$this->zipArchive = new ZipArchive();
|
||||||
|
$this->zipArchive->open(storage_path('app/private/' . $filename), ZipArchive::CREATE | ZipArchive::OVERWRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFile(string $filename) {
|
||||||
|
$filename = storage_path('app/private/' . $filename);
|
||||||
|
if (!file_exists($filename)) {
|
||||||
|
dd($filename);
|
||||||
|
}
|
||||||
|
$this->zipArchive->addFile($filename, basename($filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create() : bool {
|
||||||
|
$this->zipArchive->close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
app/RelationModels/EventEatingHabits.php
Normal file
11
app/RelationModels/EventEatingHabits.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RelationModels;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class EventEatingHabits extends CommonModel
|
||||||
|
{
|
||||||
|
protected $table = 'event_allowed_eating_habits';
|
||||||
|
protected $fillable = ['event_id', 'eating_habit_id'];
|
||||||
|
}
|
||||||
10
app/RelationModels/EventLocalGroups.php
Normal file
10
app/RelationModels/EventLocalGroups.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RelationModels;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class EventLocalGroups extends CommonModel {
|
||||||
|
protected $table = 'event_local_groups';
|
||||||
|
protected $fillable = ['event_id', 'local_group_id'];
|
||||||
|
}
|
||||||
11
app/RelationModels/EventManagers.php
Normal file
11
app/RelationModels/EventManagers.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RelationModels;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class EventManagers extends CommonModel
|
||||||
|
{
|
||||||
|
protected $table = 'event_managers';
|
||||||
|
protected $fillable = ['event_id', 'user_id'];
|
||||||
|
}
|
||||||
25
app/RelationModels/EventParticipationFee.php
Normal file
25
app/RelationModels/EventParticipationFee.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RelationModels;
|
||||||
|
|
||||||
|
use App\Casts\AmountCast;
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class EventParticipationFee extends CommonModel
|
||||||
|
{
|
||||||
|
protected $table = 'event_participation_fees';
|
||||||
|
|
||||||
|
protected $fillable = ['tenant', 'type', 'name', 'description', 'amount'];
|
||||||
|
protected $casts = ['amount' => AmountCast::class];
|
||||||
|
|
||||||
|
public function tenant() : BelongsTo {
|
||||||
|
return $this->belongsTo(Tenant::class, 'tenant', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type() : BelongsTo {
|
||||||
|
return $this->belongsTo(ParticipationFeeType::class, 'type', 'slug');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace App\Repositories;
|
|||||||
|
|
||||||
use App\Enumerations\CostUnitType;
|
use App\Enumerations\CostUnitType;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
use App\Enumerations\UserRole;
|
use App\Enumerations\UserRole;
|
||||||
use App\Models\CostUnit;
|
use App\Models\CostUnit;
|
||||||
use App\Resources\CostUnitResource;
|
use App\Resources\CostUnitResource;
|
||||||
@@ -67,6 +68,9 @@ class CostUnitRepository {
|
|||||||
$canSeeAll = false;
|
$canSeeAll = false;
|
||||||
$user = Auth()->user();
|
$user = Auth()->user();
|
||||||
|
|
||||||
|
if ($disableAccessCheck) {
|
||||||
|
$canSeeAll = true;
|
||||||
|
} else {
|
||||||
if ($tenant->slug !== 'lv') {
|
if ($tenant->slug !== 'lv') {
|
||||||
if (
|
if (
|
||||||
$user->user_role_main === UserRole::USER_ROLE_ADMIN ||
|
$user->user_role_main === UserRole::USER_ROLE_ADMIN ||
|
||||||
@@ -81,6 +85,7 @@ class CostUnitRepository {
|
|||||||
$canSeeAll = true;
|
$canSeeAll = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$visibleCostUnits = [];
|
$visibleCostUnits = [];
|
||||||
/** @var CostUnit $costUnit */
|
/** @var CostUnit $costUnit */
|
||||||
@@ -154,6 +159,25 @@ class CostUnitRepository {
|
|||||||
return $amount;
|
return $amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sumupByInvoiceType(CostUnit $costUnit, InvoiceType $invoiceType) : Amount {
|
||||||
|
$amount = new Amount(0, 'Euro');
|
||||||
|
|
||||||
|
foreach ($costUnit->invoices()->get() as $invoice) {
|
||||||
|
if (
|
||||||
|
$invoice->status === InvoiceStatus::INVOICE_STATUS_DENIED ||
|
||||||
|
$invoice->donation ||
|
||||||
|
$invoice->type !== $invoice->slug
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount->addAmount(Amount::fromString($invoice->amount));
|
||||||
|
}
|
||||||
|
return $amount;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function countInvoices(CostUnit $costUnit) : array {
|
public function countInvoices(CostUnit $costUnit) : array {
|
||||||
$returnData = [
|
$returnData = [
|
||||||
InvoiceStatus::INVOICE_STATUS_NEW => 0,
|
InvoiceStatus::INVOICE_STATUS_NEW => 0,
|
||||||
|
|||||||
60
app/Repositories/EventRepository.php
Normal file
60
app/Repositories/EventRepository.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\CostUnit;
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\Resources\CostUnitResource;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
|
class EventRepository {
|
||||||
|
public function listAll() : array {
|
||||||
|
return $this->getEventsByCriteria([
|
||||||
|
'archived' => false
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById(int $id, bool $accessCheck = true) : ?Event {
|
||||||
|
$events = self::getEventsByCriteria(['id' => $id], $accessCheck);
|
||||||
|
return $events[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEventsByCriteria(array $criteria, $accessCheck = true) : array {
|
||||||
|
$tenant = app('tenant');
|
||||||
|
|
||||||
|
$canSeeAll = false;
|
||||||
|
$user = Auth()->user();
|
||||||
|
|
||||||
|
if (!$accessCheck) {
|
||||||
|
$canSeeAll = true;
|
||||||
|
} else {
|
||||||
|
if ($tenant->slug !== 'lv') {
|
||||||
|
if (
|
||||||
|
$user->user_role_main === UserRole::USER_ROLE_ADMIN ||
|
||||||
|
in_array($user->user_role_local_group, [UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_ADMIN])
|
||||||
|
) {
|
||||||
|
$canSeeAll = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
in_array($user->user_role_main, [UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_ADMIN])
|
||||||
|
) {
|
||||||
|
$canSeeAll = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$visibleEvents = [];
|
||||||
|
/** @var Event $event */
|
||||||
|
foreach (Event::where($criteria)->get() as $event) {
|
||||||
|
|
||||||
|
if ($event->eventManagers()->where('user_id', $user->id)->exists() || $canSeeAll || !$accessCheck) {
|
||||||
|
$visibleEvents[] = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $visibleEvents;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,9 @@ use Illuminate\Database\Eloquent\Collection;
|
|||||||
class InvoiceRepository {
|
class InvoiceRepository {
|
||||||
public function getMyInvoicesWidget() : array {
|
public function getMyInvoicesWidget() : array {
|
||||||
$invoices = [
|
$invoices = [
|
||||||
InvoiceStatus::INVOICE_STATUS_NEW => ['slug' => 'new', 'title' => 'Neue Abrechnungen', 'count' => 0, 'amount' => Amount::fromString('0')->toString()],
|
InvoiceStatus::INVOICE_STATUS_NEW => ['slug' => InvoiceStatus::INVOICE_STATUS_NEW, 'title' => 'Neue Abrechnungen', 'count' => 0, 'amount' => Amount::fromString('0')->toString()],
|
||||||
InvoiceStatus::INVOICE_STATUS_APPROVED => ['slug' => 'approved', 'title' => 'Freigegebene Abrechnungen', 'count' => 0, 'amount' => Amount::fromString('0')->toString()],
|
InvoiceStatus::INVOICE_STATUS_APPROVED => ['slug' => InvoiceStatus::INVOICE_STATUS_APPROVED, 'title' => 'Freigegebene Abrechnungen', 'count' => 0, 'amount' => Amount::fromString('0')->toString()],
|
||||||
InvoiceStatus::INVOICE_STATUS_DENIED => ['slug' => 'declined', 'title' => 'Abgelehnte Abrechnungen', 'count' => 0, 'amount' => Amount::fromString('0')->toString()]
|
InvoiceStatus::INVOICE_STATUS_DENIED => ['slug' => InvoiceStatus::INVOICE_STATUS_DENIED, 'title' => 'Abgelehnte Abrechnungen', 'count' => 0, 'amount' => Amount::fromString('0')->toString()]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@@ -39,15 +39,39 @@ class InvoiceRepository {
|
|||||||
return $invoices;
|
return $invoices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByStatus(CostUnit $costUnit, string $status) : array {
|
public function getUnexportedInvoices() : Collection {
|
||||||
|
return Invoice::where(['tenant' => app('tenant')->slug, 'status' => InvoiceStatus::INVOICE_STATUS_EXPORTED, 'upload_required' => true])->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByStatus(CostUnit $costUnit, string $status, bool $forDisplay = true) : array {
|
||||||
$returnData = [];
|
$returnData = [];
|
||||||
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
||||||
|
if ($forDisplay) {
|
||||||
$returnData[] = new InvoiceResource($invoice)->toArray();
|
$returnData[] = new InvoiceResource($invoice)->toArray();
|
||||||
|
} else {
|
||||||
|
$returnData[] = $invoice;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return $returnData;
|
return $returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMyInvoicesByStatus(string $status) : array {
|
||||||
|
$returnData = [];
|
||||||
|
foreach (Invoice::where(
|
||||||
|
[
|
||||||
|
'status' => $status,
|
||||||
|
'user_id' => auth()->user()->id,
|
||||||
|
'tenant' => app('tenant')->slug,
|
||||||
|
|
||||||
|
]
|
||||||
|
)->get() as $invoice) {
|
||||||
|
$returnData[] = new InvoiceResource($invoice)->toArray();
|
||||||
|
};
|
||||||
|
return $returnData;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAsTreasurer(int $invoiceId) : ?Invoice {
|
public function getAsTreasurer(int $invoiceId) : ?Invoice {
|
||||||
$invoice = Invoice::where('id', $invoiceId)->first();
|
$invoice = Invoice::where('id', $invoiceId)->first();
|
||||||
if ($invoice === null) {
|
if ($invoice === null) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class PageTextRepository {
|
|||||||
$name = strtoupper($name);
|
$name = strtoupper($name);
|
||||||
$pageText = PageText::where(['name' => $name])->first();
|
$pageText = PageText::where(['name' => $name])->first();
|
||||||
if (null === $pageText) {
|
if (null === $pageText) {
|
||||||
PageText::create(['name' => $name, 'content' => '']);
|
PageText::create(['name' => $name, 'content' => $name]);
|
||||||
|
|
||||||
|
|
||||||
return strtoupper($name);
|
return strtoupper($name);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Resources;
|
namespace App\Resources;
|
||||||
|
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
use App\Models\CostUnit;
|
use App\Models\CostUnit;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
@@ -28,6 +29,14 @@ class CostUnitResource {
|
|||||||
$countDonatedInvoices = $countInvoices[InvoiceStatus::INVOICE_META_STATUS_DONATED];
|
$countDonatedInvoices = $countInvoices[InvoiceStatus::INVOICE_META_STATUS_DONATED];
|
||||||
$countDeniedInvoices = $countInvoices[InvoiceStatus::INVOICE_STATUS_DENIED];
|
$countDeniedInvoices = $countInvoices[InvoiceStatus::INVOICE_STATUS_DENIED];
|
||||||
|
|
||||||
|
$amounts = [];
|
||||||
|
$overAllAmount = new Amount(0, 'Euro');
|
||||||
|
foreach (InvoiceType::all() as $invoiceType) {
|
||||||
|
$overAllAmount->addAmount($costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType));
|
||||||
|
$amounts[$invoiceType->slug]['string'] = $costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType)->toString();
|
||||||
|
$amounts[$invoiceType->slug]['name'] = $invoiceType->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$data = array_merge(
|
$data = array_merge(
|
||||||
$this->costUnit->toArray(),
|
$this->costUnit->toArray(),
|
||||||
@@ -41,6 +50,8 @@ class CostUnitResource {
|
|||||||
'countDonatedInvoices' => $countDonatedInvoices,
|
'countDonatedInvoices' => $countDonatedInvoices,
|
||||||
'countDeniedInvoices' => $countDeniedInvoices,
|
'countDeniedInvoices' => $countDeniedInvoices,
|
||||||
'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,
|
||||||
|
'overAllAmount' => ['text' => $overAllAmount->toString(), 'value' => $overAllAmount],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
22
app/Resources/EatingHabitResource.php
Normal file
22
app/Resources/EatingHabitResource.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Resources;
|
||||||
|
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class EatingHabitResource extends JsonResource {
|
||||||
|
private EatingHabit $eatingHabit;
|
||||||
|
|
||||||
|
public function __construct(EatingHabit $eatingHabit) {
|
||||||
|
$this->eatingHabit = $eatingHabit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray($request) : array {
|
||||||
|
return [
|
||||||
|
'id' => $this->eatingHabit->id,
|
||||||
|
'slug' => $this->eatingHabit->slug,
|
||||||
|
'name' => $this->eatingHabit->name
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
109
app/Resources/EventResource.php
Normal file
109
app/Resources/EventResource.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Resources;
|
||||||
|
|
||||||
|
use App\Enumerations\ParticipationFeeType;
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
|
class EventResource {
|
||||||
|
private Event $event;
|
||||||
|
|
||||||
|
public function __construct(Event $event) {
|
||||||
|
$this->event = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() : array {
|
||||||
|
$duration = $this->event->end_date->diff($this->event->start_date)->d + 1;
|
||||||
|
|
||||||
|
|
||||||
|
$returnArray = [
|
||||||
|
'id' => $this->event->id,
|
||||||
|
'name' => $this->event->name,
|
||||||
|
'location' => $this->event->location,
|
||||||
|
'postalCode' => $this->event->postal_code,
|
||||||
|
'email' => $this->event->email,
|
||||||
|
'accountOwner' => $this->event->account_owner,
|
||||||
|
'accountIban' => $this->event->account_iban,
|
||||||
|
'alcoholicsAge' => $this->event->alcoholics_age,
|
||||||
|
'sendWeeklyReports' => $this->event->send_weekly_report,
|
||||||
|
'registrationAllowed' => $this->event->registration_allowed,
|
||||||
|
'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')],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true);
|
||||||
|
$returnArray['solidarityPayment'] = $this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY;
|
||||||
|
$returnArray['payPerDay'] = $this->event->pay_per_day;
|
||||||
|
$returnArray['maxAmount'] = $this->event->total_max_amount->getFormattedAmount();
|
||||||
|
$returnArray['eventBegin'] = $this->event->start_date->format('d.m.Y');
|
||||||
|
$returnArray['eventBeginInternal'] = $this->event->start_date;
|
||||||
|
$returnArray['eventEnd'] = $this->event->end_date->format('d.m.Y');
|
||||||
|
$returnArray['eventEndInternal'] = $this->event->end_date;
|
||||||
|
$returnArray['duration'] = $duration;
|
||||||
|
#
|
||||||
|
$totalBalance = new Amount(0, 'Euro');
|
||||||
|
$totalBalance->addAmount($this->calculateIncomes());
|
||||||
|
$totalBalance->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
||||||
|
|
||||||
|
$returnArray['totalIncome'] = $this->calculateIncomes()->toString();
|
||||||
|
$returnArray['totalBalance'] = ['text' => $totalBalance->toString(), 'value' => $totalBalance->getAmount()];
|
||||||
|
$returnArray['flatSupport'] = $this->event->support_flat->toString();
|
||||||
|
$returnArray['supportPerson'] = $this->event->support_per_person->toString();
|
||||||
|
|
||||||
|
$returnArray['flatSupportEdit'] = $this->event->support_flat->getFormattedAmount();
|
||||||
|
$returnArray['supportPersonEdit'] = $this->event->support_per_person->getFormattedAmount();
|
||||||
|
$returnArray['managers'] = $this->event->eventManagers()->get()->map(fn($user) => new UserResource($user))->toArray();
|
||||||
|
|
||||||
|
$returnArray['supportPersonCalced'] = $this->event->support_per_person->toString();
|
||||||
|
$returnArray['contributingLocalGroups'] = $this->event->localGroups()->get()->map(fn($localGroup) => new LocalGroupResource($localGroup))->toArray();
|
||||||
|
$returnArray['eatingHabits'] = $this->event->eatingHabits()->get()->map(
|
||||||
|
fn($eatingHabit) => new EatingHabitResource($eatingHabit))->toArray();
|
||||||
|
|
||||||
|
|
||||||
|
for ($i = 1; $i <= 4; $i++) {
|
||||||
|
$returnArray['participationFee_' . $i] = [
|
||||||
|
'active' => false,
|
||||||
|
'name' => '',
|
||||||
|
'description' => '',
|
||||||
|
'amount' => '0,00',
|
||||||
|
'type' => null
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->event->{'participation_fee_' . $i} === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$returnArray['participationFee_' . $i] = [
|
||||||
|
'active' => true,
|
||||||
|
'amount' => $this->event->{'participationFee' . $i}()->first()->amount->getFormattedAmount(),
|
||||||
|
'name' => $this->event->{'participationFee' . $i}->first()->name,
|
||||||
|
'description' => $this->event->{'participationFee' . $i}()->first()->description,
|
||||||
|
'type' => $this->event->{'participationFee' . $i}->first()->type
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
if ($this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY) {
|
||||||
|
$returnArray['participationFee_1' . $i]['description'] = '';
|
||||||
|
$returnArray['participationFee_2' . $i]['description'] = '';
|
||||||
|
$returnArray['participationFee_3' . $i]['description'] = 'Nach Verfügbarkeit';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function calculateIncomes() : Amount {
|
||||||
|
$amount = new Amount(0, 'Euro');
|
||||||
|
$amount->addAmount($this->event->support_flat);
|
||||||
|
return $amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ class InvoiceResource {
|
|||||||
$returnData['invoiceType'] .= ' (' . $this->invoice->type_other . ')';
|
$returnData['invoiceType'] .= ' (' . $this->invoice->type_other . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$returnData['invoiceTypeShort'] = $this->invoice->invoiceType()->name;
|
||||||
$returnData['costUnitName'] = $this->invoice->costUnit()->first()->name;
|
$returnData['costUnitName'] = $this->invoice->costUnit()->first()->name;
|
||||||
$returnData['invoiceNumber'] = $this->invoice->invoice_number;
|
$returnData['invoiceNumber'] = $this->invoice->invoice_number;
|
||||||
$returnData['contactName'] = $this->invoice->contact_name;
|
$returnData['contactName'] = $this->invoice->contact_name;
|
||||||
@@ -47,7 +48,17 @@ class InvoiceResource {
|
|||||||
$returnData['costUnitId'] = $this->invoice->cost_unit_id;
|
$returnData['costUnitId'] = $this->invoice->cost_unit_id;
|
||||||
$returnData['amountPlain'] = new Amount($this->invoice->amount, '')->toString();
|
$returnData['amountPlain'] = new Amount($this->invoice->amount, '')->toString();
|
||||||
$returnData['internalType'] = $this->invoice->type;
|
$returnData['internalType'] = $this->invoice->type;
|
||||||
|
$returnData['donationText'] = $this->invoice->donation ? 'Ja' : 'Nein';
|
||||||
|
$returnData['createdAt'] = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->created_at)->format('d.m.Y H:i');
|
||||||
|
$returnData['approvedAt'] = '--';
|
||||||
|
$returnData['approvedBy'] = '--';
|
||||||
|
|
||||||
|
if ($this->invoice->status === InvoiceStatus::INVOICE_STATUS_APPROVED) {
|
||||||
|
$returnData['approvedAt'] = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->approved_at)->format('d.m.Y H:i');
|
||||||
|
$returnData['approvedBy'] = $this->invoice->approvedBy()->first()->username ?? '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnData['alreadyPaid'] = $returnData['alreadyPaid'] == '' ? 0 : 1;
|
||||||
|
|
||||||
return $returnData;
|
return $returnData;
|
||||||
}
|
}
|
||||||
|
|||||||
24
app/Resources/LocalGroupResource.php
Normal file
24
app/Resources/LocalGroupResource.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Resources;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class LocalGroupResource extends JsonResource {
|
||||||
|
private Tenant $tenant;
|
||||||
|
|
||||||
|
public function __construct(Tenant $tenant) {
|
||||||
|
$this->tenant = $tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray($request) : array {
|
||||||
|
return [
|
||||||
|
'id' => $this->tenant->id,
|
||||||
|
'name' => $this->tenant->name,
|
||||||
|
'email' => $this->tenant->email,
|
||||||
|
'city' => $this->tenant->city,
|
||||||
|
'postalcode'=> $this->tenant->postcode
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,25 +2,31 @@
|
|||||||
|
|
||||||
namespace App\Scopes;
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
use App\Providers\AuthCheckProvider;
|
use App\Providers\AuthCheckProvider;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
|
use App\Repositories\EventRepository;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
use App\Repositories\PageTextRepository;
|
use App\Repositories\PageTextRepository;
|
||||||
use App\Repositories\UserRepository;
|
use App\Repositories\UserRepository;
|
||||||
|
|
||||||
abstract class CommonController {
|
abstract class CommonController {
|
||||||
|
protected Tenant $tenant;
|
||||||
protected UserRepository $users;
|
protected UserRepository $users;
|
||||||
protected CostUnitRepository $costUnits;
|
protected CostUnitRepository $costUnits;
|
||||||
|
|
||||||
protected PageTextRepository $pageTexts;
|
protected PageTextRepository $pageTexts;
|
||||||
|
|
||||||
protected InvoiceRepository $invoices;
|
protected InvoiceRepository $invoices;
|
||||||
|
protected EventRepository $events;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
$this->tenant = app('tenant');
|
||||||
$this->users = new UserRepository();
|
$this->users = new UserRepository();
|
||||||
$this->costUnits = new CostUnitRepository();
|
$this->costUnits = new CostUnitRepository();
|
||||||
$this->pageTexts = new PageTextRepository();
|
$this->pageTexts = new PageTextRepository();
|
||||||
$this->invoices = new InvoiceRepository();
|
$this->invoices = new InvoiceRepository();
|
||||||
|
$this->events = new EventRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function checkAuth() {
|
protected function checkAuth() {
|
||||||
|
|||||||
8
app/Tasks/CronTask.php
Normal file
8
app/Tasks/CronTask.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tasks;
|
||||||
|
|
||||||
|
interface CronTask
|
||||||
|
{
|
||||||
|
public function handle(): void;
|
||||||
|
}
|
||||||
31
app/Tasks/UploadInvoices.php
Normal file
31
app/Tasks/UploadInvoices.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tasks;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\UploadInvoice\UploadInvoiceCommand;
|
||||||
|
use App\Domains\Invoice\Actions\UploadInvoice\UploadInvoiceRequest;
|
||||||
|
use App\Repositories\InvoiceRepository;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class UploadInvoices implements CronTask {
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if (!app('tenant')->upload_exports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoiceRepository = new InvoiceRepository();
|
||||||
|
foreach ($invoiceRepository->getUnexportedInvoices() as $invoice) {
|
||||||
|
app('taskLogger')->info("Uploading invoice {$invoice->invoice_number}");
|
||||||
|
$request = new UploadInvoiceRequest($invoice);
|
||||||
|
$command = new UploadInvoiceCommand($request);
|
||||||
|
if ($command->execute()->success) {
|
||||||
|
app('taskLogger')->info('Upload successful');
|
||||||
|
} else {
|
||||||
|
app('taskLogger')->error('Upload failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
app('taskLogger')->info('------------------------------------');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,4 +44,11 @@ class Amount {
|
|||||||
public function subtractAmount(Amount $amount) : void {
|
public function subtractAmount(Amount $amount) : void {
|
||||||
$this->amount -= $amount->getAmount();
|
$this->amount -= $amount->getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFormattedAmount() : string {
|
||||||
|
$value = number_format( round( $this->amount, 2 ), 2, ',', '.' );
|
||||||
|
return $value
|
||||||
|
|> trim(...)
|
||||||
|
|> function (string $value) : string { return str_replace('.', ',', $value); };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<small class="error_text" v-if="props.message">{{ props.message }}</small>
|
<small style="margin-bottom: 20px;" class="error_text" v-if="props.message">{{ props.message }}</small>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
57
app/Views/Components/LoadingModal.vue
Normal file
57
app/Views/Components/LoadingModal.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<Modal :show="show" title="Bitte warten">
|
||||||
|
<div class="loading-wrapper">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Modal from './Modal.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
show: Boolean,
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: "Bitte warte während die Anfrage bearbeitet wird ..."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.loading-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px;
|
||||||
|
gap: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* klassischer Spinner */
|
||||||
|
.spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 4px solid #ddd;
|
||||||
|
border-top-color: #47a0d8;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,6 +6,11 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
// [{ title: "Titel", component: Component, endpoint: "/wp-json/..." }]
|
// [{ title: "Titel", component: Component, endpoint: "/wp-json/..." }]
|
||||||
|
},
|
||||||
|
subTabIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,7 +59,7 @@ async function selectTab(index) {
|
|||||||
// ersten Tab automatisch laden
|
// ersten Tab automatisch laden
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.tabs.length > 0) {
|
if (props.tabs.length > 0) {
|
||||||
selectTab(0)
|
selectTab(props.subTabIndex)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
"php": "^8.5",
|
"php": "^8.5",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^2.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1"
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"contelli/webdav-sync": "^1.0",
|
||||||
|
"ext-zip": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
@@ -18,7 +21,11 @@
|
|||||||
"laravel/sail": "^1.41",
|
"laravel/sail": "^1.41",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.6",
|
"nunomaduro/collision": "^8.6",
|
||||||
"phpunit/phpunit": "^11.5.3"
|
"phpunit/phpunit": "^11.5.3",
|
||||||
|
"dompdf/dompdf": "^3.0",
|
||||||
|
"setasign/fpdf": "^1.8",
|
||||||
|
"setasign/fpdi": "^2.6",
|
||||||
|
"maennchen/zipstream-php": "^3.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user