Managing own invoices
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->invoice->save()) {
|
if ($this->request->invoice->save()) {
|
||||||
|
|||||||
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>
|
||||||
@@ -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>
|
||||||
100
app/Domains/Invoice/Views/Partials/myInvoiceDetails/Header.vue
Normal file
100
app/Domains/Invoice/Views/Partials/myInvoiceDetails/Header.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<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;">
|
||||||
|
<button
|
||||||
|
v-if="props.data.status === 'new' && modeShow"
|
||||||
|
@click="emit('reject')"
|
||||||
|
class="button mareike-button mareike-deny-button"
|
||||||
|
>
|
||||||
|
Abrechnung zurückziehen
|
||||||
|
</button>
|
||||||
|
<button v-if="props.data.status === 'denied' && modeShow"
|
||||||
|
@click="emit('delete')"
|
||||||
|
class="button mareike-button mareike-deny-button"
|
||||||
|
>
|
||||||
|
Abrechnung Endgültig löschen
|
||||||
|
</button>
|
||||||
|
</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>
|
||||||
@@ -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()]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +48,21 @@ class InvoiceRepository {
|
|||||||
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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user