Displaying estimates
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Controllers;
|
||||
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListController extends CommonController
|
||||
{
|
||||
public function __invoke(int $costUnitId, string $estimateType, Request $request) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
$estimates = $this->estimates->getEstimates($costUnit, $estimateType);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnitId' => $costUnitId,
|
||||
'title' => InvoiceType::where('slug', $estimateType)->first()->name,
|
||||
'estimates' => $estimates,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Budget\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
|
||||
class MainController extends CommonController
|
||||
{
|
||||
public function __invoke(int $costUnitId, Request $request) : Response
|
||||
{
|
||||
$inertiaProvider = new InertiaProvider('Budget/List', [
|
||||
'cost_unit_id' => $costUnitId
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Budget\Controllers\ListController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('api/v1')->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('budget')->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::prefix('/{costUnitId}')->group(function () {
|
||||
Route::get('/list/{estimateType}', ListController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Budget\Controllers\MainController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('budget')->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::prefix('/{costUnitId}')->group(function() {
|
||||
Route::get('/', MainController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup>
|
||||
import {reactive, inject, onMounted} from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
import ListBudgets from "./ListBudgetTypes.vue";
|
||||
|
||||
const props = defineProps({
|
||||
message: String,
|
||||
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
cost_unit_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
})
|
||||
|
||||
// Prüfen, ob ein ?id= Parameter in der URL übergeben wurde
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.cost_unit_id
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Verpflegung',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/catering",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Unterkunft',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/accommodation",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Programm',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/program",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Logistik',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/logistic",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Technik',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/technical",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Reisekosten',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/travelling",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Verwaltung',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/management",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
{
|
||||
title: 'Sonstiges',
|
||||
component: ListBudgets,
|
||||
endpoint: "/api/v1/budget/" + props.cost_unit_id + "/list/other",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Veranstaltungsbudget">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,195 @@
|
||||
<script setup>
|
||||
import {createApp, ref} from 'vue'
|
||||
import LoadingModal from "../../../Views/Components/LoadingModal.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
|
||||
deep_jump_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
|
||||
deep_jump_id_sub: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const showInvoiceList = ref(false)
|
||||
const invoices = ref(null)
|
||||
const current_cost_unit = ref(null)
|
||||
const showLoading = ref(false)
|
||||
const show_invoice = ref(false)
|
||||
const invoice = ref(null)
|
||||
|
||||
const show_cost_unit = ref(false)
|
||||
const showTreasurers = ref(false)
|
||||
const costUnit = ref(null)
|
||||
|
||||
const { data, loading, error, request, download } = useAjax()
|
||||
|
||||
async function costUnitDetails(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnit.value = data.costUnit
|
||||
show_cost_unit.value = true
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function editTreasurers(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/treasurers', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnit.value = data.costUnit
|
||||
showTreasurers.value = true
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
function loadInvoices(cost_unit_id) {
|
||||
window.location.href = '/cost-unit/' + cost_unit_id;
|
||||
}
|
||||
|
||||
async function denyNewRequests(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'close');
|
||||
}
|
||||
|
||||
|
||||
async function archiveCostUnit(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'archive');
|
||||
}
|
||||
|
||||
|
||||
async function allowNewRequests(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'open');
|
||||
}
|
||||
|
||||
|
||||
async function changeCostUnitState(costUnitId, endPoint) {
|
||||
showLoading.value = true;
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/' + endPoint, {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
document.getElementById('costUnitBox_' + costUnitId).style.display = 'none';
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function exportPayouts(costUnitId) {
|
||||
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 {
|
||||
if (data.tenant.download_exports) {
|
||||
const response = await fetch(exportUrl, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = downloadUrl;
|
||||
a.download = "Abrechnungen-Sippenstunden.zip";
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(a);
|
||||
}, 100);
|
||||
} else {
|
||||
const response = await request(exportUrl, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
toast.success(response.message);
|
||||
}
|
||||
showLoading.value = false;
|
||||
|
||||
} catch (err) {
|
||||
showLoading.value = false;
|
||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.data.estimates && props.data.estimates.length > 0">
|
||||
<h2>{{ props.data.title }}</h2>
|
||||
<span v-for="estimate in props.data.estimates">
|
||||
<table style="width: 100%">
|
||||
|
||||
<tr><th style="width: 200px;">
|
||||
{{ estimate.title }}
|
||||
</th>
|
||||
<td>{{ estimate.totalAmountString }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
Bearbeiten
|
||||
Löschen
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
|
||||
<CostUnitDetails :data="costUnit" :showCostUnit="show_cost_unit" v-if="show_cost_unit" @close="show_cost_unit = false" />
|
||||
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="showInvoiceList">
|
||||
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<strong style="width: 100%; text-align: center; display: block; margin-top: 20px;">
|
||||
Noch keine geschätzten Ausgaben vorhanden
|
||||
</strong>
|
||||
</div>
|
||||
<label class="link">
|
||||
Hinzufügen
|
||||
</label>
|
||||
<LoadingModal :show="showLoading" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.costunit-list {
|
||||
width: 96% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\AmountCast;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Resources\EventResource;
|
||||
use App\Scopes\InstancedModel;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
|
||||
class CostUnitEstimate extends InstancedModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'tenant',
|
||||
'cost_unit_id',
|
||||
'type',
|
||||
'description',
|
||||
'flat_amount',
|
||||
'amount_by_user',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'flat_amount' => AmountCast::class,
|
||||
'amount_by_user' => AmountCast::class,
|
||||
];
|
||||
|
||||
public function costUnit() : BelongsTo{
|
||||
return $this->belongsTo(CostUnit::class);
|
||||
}
|
||||
|
||||
public function invoiceType() : InvoiceType {
|
||||
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
||||
}
|
||||
|
||||
public function calculateAmount() : ?Amount {
|
||||
switch (true) {
|
||||
case $this->flat_amount !== null:
|
||||
return $this->flat_amount;
|
||||
default:
|
||||
$event = $this->costUnit()->first()->event()?->first();
|
||||
if (null !== $event) {
|
||||
|
||||
$participants = $event->participants()->count();
|
||||
return $this->amount_by_user->multiply($participants);
|
||||
|
||||
} else {
|
||||
dd('U');
|
||||
return $this->amount_by_user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class EstimatesRepository {
|
||||
public function getEstimates(CostUnit $costUnit, string $estimateType) : array {
|
||||
$return = [];
|
||||
foreach ($costUnit->estimates()->where('type', $estimateType)->get() as $estimate) {
|
||||
$return[] = $estimate->toResource()->toArray(request());
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Resources;
|
||||
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Models\CostUnitEstimate;
|
||||
use App\Models\EventParticipant;
|
||||
use App\ValueObjects\Age;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CostUnitEstimateResource extends JsonResource
|
||||
{
|
||||
function __construct(CostUnitEstimate $estimate)
|
||||
{
|
||||
parent::__construct($estimate);
|
||||
}
|
||||
|
||||
public function toArray($request) : array
|
||||
{
|
||||
$amountString = $this->resource->flat_amount?->toString();
|
||||
if ($amountString === null) {
|
||||
$amountString = $this->resource->amount_by_user?->toString() . ' / Person';
|
||||
} else {
|
||||
$amountString .= ' Gesamt';
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->resource->id,
|
||||
'title' => $this->resource->description,
|
||||
'totalAmountString' => $amountString,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user