From 28ffbdb696e8c3bf60f547547442c2edc60ca83e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=BCnther?=
Date: Tue, 26 May 2026 18:12:42 +0200
Subject: [PATCH] Implemented Event Budget
---
.../CreateEstimate/CreateEstimateAction.php | 50 +++++
.../CreateEstimate/CreateEstimateRequest.php | 19 ++
.../CreateEstimate/CreateEstimateResponse.php | 13 ++
.../DeleteEstimate/DeleteEstimateAction.php | 16 ++
.../DeleteEstimate/DeleteEstimateRequest.php | 12 ++
.../DeleteEstimate/DeleteEstimateResponse.php | 12 ++
.../Budget/Controllers/DeleteController.php | 43 ++++
.../Budget/Controllers/ListController.php | 2 +
.../Budget/Controllers/SaveController.php | 47 +++++
app/Domains/Budget/Routes/api.php | 4 +
.../Budget/Views/AddOrUpdateEstimate.vue | 100 +++++++++
app/Domains/Budget/Views/ListBudgetTypes.vue | 190 ++++++------------
.../CostUnit/Views/Partials/ListInvoices.vue | 1 -
app/Models/CostUnitEstimate.php | 6 +-
app/Repositories/EstimatesRepository.php | 28 +++
app/Resources/CostUnitEstimateResource.php | 20 +-
version | 2 +-
17 files changed, 422 insertions(+), 143 deletions(-)
create mode 100644 app/Domains/Budget/Actions/CreateEstimate/CreateEstimateAction.php
create mode 100644 app/Domains/Budget/Actions/CreateEstimate/CreateEstimateRequest.php
create mode 100644 app/Domains/Budget/Actions/CreateEstimate/CreateEstimateResponse.php
create mode 100644 app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateAction.php
create mode 100644 app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateRequest.php
create mode 100644 app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateResponse.php
create mode 100644 app/Domains/Budget/Controllers/DeleteController.php
create mode 100644 app/Domains/Budget/Controllers/SaveController.php
create mode 100644 app/Domains/Budget/Views/AddOrUpdateEstimate.vue
diff --git a/app/Domains/Budget/Actions/CreateEstimate/CreateEstimateAction.php b/app/Domains/Budget/Actions/CreateEstimate/CreateEstimateAction.php
new file mode 100644
index 0000000..0f0cf21
--- /dev/null
+++ b/app/Domains/Budget/Actions/CreateEstimate/CreateEstimateAction.php
@@ -0,0 +1,50 @@
+response = new CreateEstimateResponse();
+
+ $amount = [];
+ switch ($this->request->amountType) {
+ case 'flat':
+ $amount['flat_amount'] = $this->request->amount;
+ break;
+ case 'per_person':
+ $amount['amount_by_user'] = $this->request->amount;
+ break;
+ }
+
+ if ($this->request->estimateId === 0) {
+ $estimate = CostUnitEstimate::create(array_merge([
+ 'tenant' => app('tenant')->slug,
+ 'cost_unit_id' => $this->request->costUnit->id,
+ 'type' => $this->request->estimateType,
+ 'description' => $this->request->description,
+ ], $amount));
+ } else {
+ $estimate = CostUnitEstimate::find($this->request->estimateId);
+ $estimate->update(array_merge([
+ 'tenant' => app('tenant')->slug,
+ 'cost_unit_id' => $this->request->costUnit->id,
+ 'type' => $this->request->estimateType,
+ 'description' => $this->request->description,
+ ], $amount));
+ }
+
+ if ($estimate !== null) {
+ $this->response->estimateId = $estimate->id;
+ $this->response->success = true;
+ }
+
+ return $this->response;
+ }
+}
diff --git a/app/Domains/Budget/Actions/CreateEstimate/CreateEstimateRequest.php b/app/Domains/Budget/Actions/CreateEstimate/CreateEstimateRequest.php
new file mode 100644
index 0000000..c34d618
--- /dev/null
+++ b/app/Domains/Budget/Actions/CreateEstimate/CreateEstimateRequest.php
@@ -0,0 +1,19 @@
+success = false;
+ $this->estimateId = null;
+ }
+}
diff --git a/app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateAction.php b/app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateAction.php
new file mode 100644
index 0000000..0f8e87e
--- /dev/null
+++ b/app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateAction.php
@@ -0,0 +1,16 @@
+request->estimate->delete();
+ $response->success = true;
+ return $response;
+ }
+}
diff --git a/app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateRequest.php b/app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateRequest.php
new file mode 100644
index 0000000..85a6476
--- /dev/null
+++ b/app/Domains/Budget/Actions/DeleteEstimate/DeleteEstimateRequest.php
@@ -0,0 +1,12 @@
+success = false;
+ }
+}
diff --git a/app/Domains/Budget/Controllers/DeleteController.php b/app/Domains/Budget/Controllers/DeleteController.php
new file mode 100644
index 0000000..612f7de
--- /dev/null
+++ b/app/Domains/Budget/Controllers/DeleteController.php
@@ -0,0 +1,43 @@
+estimates->getById($estimateId);
+
+ if ($estimate === null) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Estimate not found'
+ ], 404);
+ }
+
+ $deleteEstimateResponse =
+ new DeleteEstimateAction(request: new DeleteEstimateRequest($estimate)
+ )->execute();
+
+ if ($deleteEstimateResponse->success) {
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Der Eintrag wurde erfolgreich gelöscht.'
+ ]);
+ } else {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Beim Löschen des Eintrags ist ein Fehler aufgetreten.'
+ ]);
+ }
+ }
+}
diff --git a/app/Domains/Budget/Controllers/ListController.php b/app/Domains/Budget/Controllers/ListController.php
index 7d0741e..e93602b 100644
--- a/app/Domains/Budget/Controllers/ListController.php
+++ b/app/Domains/Budget/Controllers/ListController.php
@@ -17,7 +17,9 @@ class ListController extends CommonController
'status' => 'success',
'costUnitId' => $costUnitId,
'title' => InvoiceType::where('slug', $estimateType)->first()->name,
+ 'estimateType' => $estimateType,
'estimates' => $estimates,
+ 'totalAmountString' => $this->estimates->getTotalAmount($costUnit, $estimateType)->toString(),
]);
}
diff --git a/app/Domains/Budget/Controllers/SaveController.php b/app/Domains/Budget/Controllers/SaveController.php
new file mode 100644
index 0000000..72c4e1b
--- /dev/null
+++ b/app/Domains/Budget/Controllers/SaveController.php
@@ -0,0 +1,47 @@
+costUnits->getById($costUnitId);
+
+ if ($costUnit === null) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Cost unit not found'
+ ], 404);
+ }
+
+ $createCostUniResponse =
+ new CreateEstimateAction(request: new CreateEstimateRequest(
+ description: $request->input('description'),
+ amount: Amount::fromString($request->input('amount')),
+ amountType: $request->input('amount_type'),
+ estimateType: $request->input('estimateType'),
+ costUnit: $costUnit,
+ estimateId: $request->input('estimateId'),
+ ))->execute();
+
+ if ($createCostUniResponse->success) {
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Der Eintrag wurde erfolgreich angelegt.'
+ ]);
+ } else {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Beim Anlegen des Eintrags ist ein Fehler aufgetreten.'
+ ]);
+ }
+ }
+}
diff --git a/app/Domains/Budget/Routes/api.php b/app/Domains/Budget/Routes/api.php
index 2a7d345..d01a5ee 100644
--- a/app/Domains/Budget/Routes/api.php
+++ b/app/Domains/Budget/Routes/api.php
@@ -1,5 +1,7 @@
group(function () {
Route::middleware(['auth'])->group(function () {
Route::prefix('/{costUnitId}')->group(function () {
Route::get('/list/{estimateType}', ListController::class);
+ Route::get('{estimateId}/delete', DeleteController::class);
+ Route::post('/save-estimate', SaveController::class);
});
});
});
diff --git a/app/Domains/Budget/Views/AddOrUpdateEstimate.vue b/app/Domains/Budget/Views/AddOrUpdateEstimate.vue
new file mode 100644
index 0000000..16cfc5a
--- /dev/null
+++ b/app/Domains/Budget/Views/AddOrUpdateEstimate.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/Domains/Budget/Views/ListBudgetTypes.vue b/app/Domains/Budget/Views/ListBudgetTypes.vue
index bf58ca6..7073d92 100644
--- a/app/Domains/Budget/Views/ListBudgetTypes.vue
+++ b/app/Domains/Budget/Views/ListBudgetTypes.vue
@@ -3,189 +3,115 @@ import {createApp, ref} from 'vue'
import LoadingModal from "../../../Views/Components/LoadingModal.vue";
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
import {toast} from "vue3-toastify";
+import AddOrUpdateEstimate from "./AddOrUpdateEstimate.vue";
const props = defineProps({
data: {
type: [Array, Object],
default: () => []
},
-
- 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 localData = ref(props.data)
-const show_cost_unit = ref(false)
-const showTreasurers = ref(false)
-const costUnit = ref(null)
+const showAddEstimate = ref(false)
+const estimateId = ref(null)
+const description = ref(null)
+const amount = ref(null)
+const amountType = ref(null)
const { data, loading, error, request, download } = useAjax()
-async function costUnitDetails(costUnitId) {
- const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
- method: "GET",
- });
+async function reload() {
+ const url = "/api/v1/budget/" + props.data.costUnitId + "/list/" + props.data.estimateType
+ try {
+ const response = await fetch(url, { method: 'GET' })
+ if (!response.ok) throw new Error('Fehler beim Laden')
- showLoading.value = false;
-
- if (data.status === 'success') {
- costUnit.value = data.costUnit
- show_cost_unit.value = true
- } else {
- toast.error(data.message);
+ const result = await response.json()
+ localData.value = result
+ } catch (err) {
+ console.error('Error fetching estimates:', err)
}
}
-async function editTreasurers(costUnitId) {
- const data = await request('/api/v1/cost-unit/' + costUnitId + '/treasurers', {
+async function openAddEstimate() {
+ estimateId.value = 0
+ amount.value = 0.00
+ amountType.value = 'flat'
+ description.value = ''
+ showAddEstimate.value = true
+}
+
+async function openEditEstimate(localEstimateId, localDescription, localAmount, localAmountType, localEstimateType) {
+ estimateId.value = localEstimateId
+ description.value = localDescription
+ amount.value = localAmount
+ amountType.value = localAmountType
+ console.log(localEstimateId, localDescription, localAmount, localAmountType, localEstimateType)
+ console.log(estimateId.value, description.value, amount.value, amountType.value, localEstimateType)
+ showAddEstimate.value = true
+}
+
+async function deleteEstimate(currentEstimateId) {
+ const data = await request('/api/v1/budget/' + props.data.costUnitId + '/' + currentEstimateId + '/delete', {
method: "GET",
});
- 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';
+ reload()
} 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.');
- }
-}
-
-
+
{{ props.data.title }}
-
-
-
+ Gesamtkosten: {{ localData.totalAmountString }}
+
+
|
{{ estimate.title }}
|
- {{ estimate.totalAmountString }} |
+ {{ estimate.singleAmountString }} |
|
-
- Bearbeiten
- Löschen
+ |
+
+
|
-
-
-
-
-
-
-
-
Noch keine geschätzten Ausgaben vorhanden
-