Operation processes on invoices
This commit is contained in:
@@ -18,7 +18,7 @@ class ChangeCostUnitTreasurersCommand {
|
||||
$this->request->costUnit->resetTreasurers();
|
||||
|
||||
foreach ($this->request->treasurers as $treasurer) {
|
||||
$this->request->costUnit->tresurers()->attach($treasurer);
|
||||
$this->request->costUnit->treasurers()->attach($treasurer);
|
||||
}
|
||||
|
||||
$this->request->costUnit->save();
|
||||
|
||||
@@ -9,8 +9,6 @@ use Illuminate\Http\Request;
|
||||
|
||||
class ListController extends CommonController {
|
||||
public function __invoke() {
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/List', [
|
||||
'cost_unit_id' => 1
|
||||
]);
|
||||
|
||||
28
app/Domains/CostUnit/Controllers/OpenController.php
Normal file
28
app/Domains/CostUnit/Controllers/OpenController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class OpenController extends CommonController {
|
||||
public function __invoke(int $costUnitId) {
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/Open', [
|
||||
'costUnitId' => $costUnitId
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function listInvoices(int $costUnitId, string $invoiceStatus) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
$invoices = $this->invoices->getByStatus($costUnit, $invoiceStatus);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => $costUnit,
|
||||
'invoices' => $invoices,
|
||||
'endpoint' => $invoiceStatus,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||
use App\Domains\CostUnit\Controllers\EditController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\CostUnit\Controllers\OpenController;
|
||||
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@@ -21,6 +22,11 @@ Route::prefix('api/v1')
|
||||
|
||||
|
||||
Route::prefix('/{costUnitId}') ->group(function () {
|
||||
Route::get('/invoice-list/{invoiceStatus}', [OpenController::class, 'listInvoices']);
|
||||
|
||||
|
||||
|
||||
|
||||
Route::post('/close', [ChangeStateController::class, 'close']);
|
||||
Route::post('/open', [ChangeStateController::class, 'open']);
|
||||
Route::post('/archive', [ChangeStateController::class, 'archive']);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\CostUnit\Controllers\OpenController;
|
||||
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||
use App\Domains\UserManagement\Controllers\LoginController;
|
||||
use App\Domains\UserManagement\Controllers\LogOutController;
|
||||
@@ -14,6 +15,9 @@ Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/create', [CreateController::class, 'showForm']);
|
||||
Route::get('/list', ListController::class);
|
||||
Route::get('/{costUnitId}/', OpenController::class);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@ import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
|
||||
import ListCostUnits from "./Partials/ListCostUnits.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
message: String,
|
||||
|
||||
|
||||
67
app/Domains/CostUnit/Views/Open.vue
Normal file
67
app/Domains/CostUnit/Views/Open.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<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/ListInvoices.vue";
|
||||
|
||||
const props = defineProps({
|
||||
costUnitId: Number
|
||||
})
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.cost_unit_id
|
||||
const initialInvoiceId = props.invoice_id
|
||||
|
||||
|
||||
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Neue Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/new",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Nichtexportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/approved",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Exportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/exported",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Abgelehnte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/denied",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Abrechnungen">
|
||||
<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" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -46,7 +46,7 @@ const costUnit = ref(null)
|
||||
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)
|
||||
// open_invoice_list(props.deep_jump_id, 'new', props.deep_jump_id_sub)
|
||||
}
|
||||
|
||||
async function costUnitDetails(costUnitId) {
|
||||
@@ -79,17 +79,8 @@ async function editTreasurers(costUnitId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function open_invoice_list(cost_unit_id, endpoint, invoice_id) {
|
||||
const url = '' // `/wp-json/mareike/invoices/list-${endpoint}?invoice_id=${invoice_id}&cost_unit_id=${cost_unit_id}
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
if (!response.ok) throw new Error('Fehler beim Laden')
|
||||
invoices.value = await response.json()
|
||||
current_cost_unit.value = cost_unit_id
|
||||
invoice_id = invoice_id
|
||||
showInvoiceList.value = true
|
||||
} catch (err) {
|
||||
}
|
||||
function loadInvoices(cost_unit_id) {
|
||||
window.location.href = '/cost-unit/' + cost_unit_id;
|
||||
}
|
||||
|
||||
async function denyNewRequests(costUnitId) {
|
||||
@@ -182,7 +173,7 @@ async function export_payouts(cost_unit_id) {
|
||||
<th>Unbearbeitet</th>
|
||||
<td>{{ costUnit.countNewInvoices }}</td>
|
||||
<td rowspan="4" style="vertical-align: top;">
|
||||
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" />
|
||||
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
||||
<input v-else type="button" value="Abrechnungen einsehen" />
|
||||
<br />
|
||||
<input v-if="!costUnit.archived" type="button" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||
|
||||
80
app/Domains/CostUnit/Views/Partials/ListInvoices.vue
Normal file
80
app/Domains/CostUnit/Views/Partials/ListInvoices.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup>
|
||||
import Icon from "../../../../Views/Components/Icon.vue";
|
||||
import InvoiceDetails from "../../../Invoice/Views/Partials/invoiceDetails/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/cost-unit/" + props.data.costUnit.id + "/invoice-list/" + 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.costUnit.name}}</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>
|
||||
@@ -24,7 +24,7 @@ onMounted(async () => {
|
||||
</tr>
|
||||
|
||||
<tr v-for="costUnit in costUnits.openCostUnits">
|
||||
<td>{{costUnit.name}}</td>
|
||||
<td><a :href="'/cost-unit/' + costUnit.id" class="link">{{costUnit.name}}</a></td>
|
||||
<td>{{costUnit.new_invoices_count}}</td>
|
||||
<td>{{costUnit.approved_invoices_count}}</td>
|
||||
<td>{{costUnit.totalAmount}}</td>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\ChangeStatus;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
|
||||
class ChangeStatusCommand {
|
||||
private ChangeStatusRequest $request;
|
||||
|
||||
public function __construct(ChangeStatusRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeStatusResponse {
|
||||
$response = new ChangeStatusResponse();
|
||||
|
||||
switch ($this->request->status) {
|
||||
case InvoiceStatus::INVOICE_STATUS_APPROVED:
|
||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_APPROVED;
|
||||
$this->request->invoice->approved_by = auth()->user()->id;
|
||||
$this->request->invoice->approved_at = now();
|
||||
break;
|
||||
|
||||
case InvoiceStatus::INVOICE_STATUS_DENIED:
|
||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DENIED;
|
||||
$this->request->invoice->denied_by = auth()->user()->id;
|
||||
$this->request->invoice->denied_at = now();
|
||||
$this->request->invoice->denied_reason = $this->request->comment;
|
||||
break;
|
||||
case InvoiceStatus::INVOICE_STATUS_NEW:
|
||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_NEW;
|
||||
$this->request->invoice->approved_by = null;
|
||||
$this->request->invoice->approved_at = null;
|
||||
$this->request->invoice->denied_by = null;
|
||||
$this->request->invoice->denied_at = null;
|
||||
$this->request->invoice->comment = $this->request->invoice->denied_reason;
|
||||
$this->request->invoice->denied_reason = null;
|
||||
break;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($this->request->invoice->save()) {
|
||||
$response->success = true;
|
||||
}
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\ChangeStatus;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
class ChangeStatusRequest {
|
||||
public Invoice $invoice;
|
||||
public string $status;
|
||||
public ?string $comment;
|
||||
|
||||
public function __construct(Invoice $invoice, string $status, ?string $comment = null) {
|
||||
$this->invoice = $invoice;
|
||||
$this->status = $status;
|
||||
$this->comment = $comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\ChangeStatus;
|
||||
|
||||
class ChangeStatusResponse {
|
||||
public bool $success;
|
||||
|
||||
function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class CreateInvoiceCommand {
|
||||
'travel_direction' => $this->request->travelRoute,
|
||||
'passengers' => $this->request->passengers,
|
||||
'transportation' => $this->request->transportations,
|
||||
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->path : null,
|
||||
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->fullPath : null,
|
||||
]);
|
||||
|
||||
if ($invoice !== null) {
|
||||
|
||||
@@ -59,6 +59,11 @@ class CreateInvoiceRequest {
|
||||
$this->totalAmount = $totalAmount;
|
||||
$this->isDonation = $isDonation;
|
||||
$this->userId = $userId;
|
||||
|
||||
if ($accountIban === 'undefined') {
|
||||
$this->accountIban = null;
|
||||
$this->accountOwner = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
|
||||
class UpdateInvoiceCommand {
|
||||
private UpdateInvoiceRequest $request;
|
||||
|
||||
public function __construct(UpdateInvoiceRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UpdateInvoiceResponse {
|
||||
$response = new UpdateInvoiceResponse();
|
||||
|
||||
$this->request->invoice->amount = $this->request->amount->getAmount();
|
||||
$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->save();
|
||||
|
||||
$request = new ChangeStatusRequest($this->request->invoice, InvoiceStatus::INVOICE_STATUS_APPROVED);
|
||||
$changeStatusCommand = new ChangeStatusCommand($request);
|
||||
$changeStatusCommand->execute();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
||||
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Models\CostUnit;
|
||||
use App\Models\Invoice;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class UpdateInvoiceRequest {
|
||||
public string $comment;
|
||||
public string $invoiceType;
|
||||
public CostUnit $costUnit;
|
||||
public Invoice $invoice;
|
||||
public Amount $amount;
|
||||
|
||||
public function __construct(Invoice $invoice, string $comment, string $invoiceType, CostUnit $costUnit, Amount $amount) {
|
||||
$this->comment = $comment;
|
||||
$this->invoiceType = $invoiceType;
|
||||
$this->costUnit = $costUnit;
|
||||
$this->invoice = $invoice;
|
||||
$this->amount = $amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
class UpdateInvoiceResponse {
|
||||
public bool $success;
|
||||
public ?Invoice $invoice;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->invoice = null;
|
||||
}
|
||||
}
|
||||
27
app/Domains/Invoice/Controllers/ChangeStateController.php
Normal file
27
app/Domains/Invoice/Controllers/ChangeStateController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ChangeStateController extends CommonController
|
||||
{
|
||||
public function __invoke(int $invoiceId, string $newState) : JsonResponse {
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if ($invoice === null) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$comment = request()->get('reason') ?? null;
|
||||
$changeStatusRequest = new ChangeStatusRequest($invoice, $newState, $comment);
|
||||
$changeStatusCommand = new ChangeStatusCommand($changeStatusRequest);
|
||||
if ($changeStatusCommand->execute()->success) {
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
||||
132
app/Domains/Invoice/Controllers/EditController.php
Normal file
132
app/Domains/Invoice/Controllers/EditController.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceCommand;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceCommand;
|
||||
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Resources\InvoiceResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use App\ValueObjects\InvoiceFile;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EditController extends CommonController{
|
||||
public function copyInvoice(int $invoiceId) : JsonResponse{
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if ($invoice === null) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$receiptfile = null;
|
||||
if ($invoice->document_filename !== null) {
|
||||
$receiptfile = new InvoiceFile();
|
||||
$receiptfile->filename = $invoice->document_filename;
|
||||
$receiptfile->fullPath = $invoice->document_filename;
|
||||
}
|
||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||
$invoice->costUnit()->first(),
|
||||
$invoice->contact_name,
|
||||
$invoice->type,
|
||||
$invoice->amount,
|
||||
$receiptfile,
|
||||
$invoice->donation,
|
||||
$invoice->user_id,
|
||||
$invoice->contact_email,
|
||||
$invoice->contact_phone,
|
||||
$invoice->contact_bank_owner,
|
||||
$invoice->contact_bank_iban,
|
||||
$invoice->type_other,
|
||||
$invoice->travel_direction,
|
||||
$invoice->distance,
|
||||
$invoice->passengers,
|
||||
$invoice->transportation
|
||||
);
|
||||
|
||||
$invoiceCreationCommand = new CreateInvoiceCommand($createInvoiceRequest);
|
||||
$newInvoice = $invoiceCreationCommand->execute()->invoice;
|
||||
|
||||
$invoiceDenyRequest = new ChangeStatusRequest($invoice,InvoiceStatus::INVOICE_STATUS_DENIED, 'Abrechnungskorrektur in Rechnungsnummer #' . $newInvoice->invoice_number . ' erstellt.');
|
||||
$invoiceDenyCommand = new ChangeStatusCommand($invoiceDenyRequest);
|
||||
$invoiceDenyCommand->execute();
|
||||
|
||||
|
||||
$runningJobs = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_RUNNING_JOB);
|
||||
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
||||
|
||||
return response()->json([
|
||||
'invoice' => new InvoiceResource($invoice)->toArray(),
|
||||
'status' => 'success',
|
||||
'costUnits' => array_merge($runningJobs, $currentEvents),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateInvoice(int $invoiceId, Request $request) : JsonResponse {
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if ($invoice === null) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$modifyData = $request->get('invoiceData');
|
||||
|
||||
$newAmount = Amount::fromString($modifyData['amount']);
|
||||
$amountLeft = Amount::fromString($invoice->amount);
|
||||
$amountLeft->subtractAmount($newAmount);
|
||||
|
||||
$newCostUnit = $this->costUnits->getById($modifyData['cost_unit'],true);
|
||||
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
||||
$invoice,
|
||||
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
||||
$modifyData['type_internal'],
|
||||
$newCostUnit,
|
||||
$newAmount
|
||||
);
|
||||
$updateInvoiceCommand = new UpdateInvoiceCommand($updateInvoiceRequest);
|
||||
$updateInvoiceCommand->execute();
|
||||
|
||||
|
||||
$newInvoice = null;
|
||||
if (isset($modifyData['duplicate']) && $modifyData['duplicate'] === true) {
|
||||
$receiptfile = null;
|
||||
if ($invoice->document_filename !== null) {
|
||||
$receiptfile = new InvoiceFile();
|
||||
$receiptfile->filename = $invoice->document_filename;
|
||||
$receiptfile->fullPath = $invoice->document_filename;
|
||||
}
|
||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||
$invoice->costUnit()->first(),
|
||||
$invoice->contact_name,
|
||||
$invoice->type,
|
||||
$amountLeft->getAmount(),
|
||||
$receiptfile,
|
||||
$invoice->donation,
|
||||
$invoice->user_id,
|
||||
$invoice->contact_email,
|
||||
$invoice->contact_phone,
|
||||
$invoice->contact_bank_owner,
|
||||
$invoice->contact_bank_iban,
|
||||
$invoice->type_other,
|
||||
$invoice->travel_direction,
|
||||
$invoice->distance,
|
||||
$invoice->passengers,
|
||||
$invoice->transportation
|
||||
);
|
||||
|
||||
$invoiceCreationCommand = new CreateInvoiceCommand($createInvoiceRequest);
|
||||
$newInvoice = $invoiceCreationCommand->execute()->invoice;
|
||||
}
|
||||
|
||||
$useInvoice = $newInvoice ?? $invoice;
|
||||
$do_copy = $newInvoice !== null ? true : false;
|
||||
return response()->json([
|
||||
'invoice' => new InvoiceResource($useInvoice)->toArray(),
|
||||
'do_copy' => $do_copy,
|
||||
]);
|
||||
}
|
||||
}
|
||||
55
app/Domains/Invoice/Controllers/ShowInvoiceController.php
Normal file
55
app/Domains/Invoice/Controllers/ShowInvoiceController.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Resources\InvoiceResource;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class ShowInvoiceController extends CommonController {
|
||||
public function __invoke(int $invoiceId) : JsonResponse{
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
|
||||
$runningJobs = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_RUNNING_JOB);
|
||||
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
||||
|
||||
return response()->json([
|
||||
'invoice' => new InvoiceResource($invoice)->toArray(),
|
||||
'costUnits' => array_merge($runningJobs, $currentEvents),
|
||||
]);
|
||||
}
|
||||
|
||||
public function showReceipt(int $invoiceId): BinaryFileResponse
|
||||
{
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if (null === $invoice) {
|
||||
abort(404, 'Datei nicht gefunden');
|
||||
}
|
||||
|
||||
if (null === $invoice->document_filename) {
|
||||
abort(404, 'Datei nicht gefunden');
|
||||
}
|
||||
|
||||
$path = $invoice->document_filename;
|
||||
// Pfad zur Datei
|
||||
$fullPath = 'private/' . $path;
|
||||
|
||||
|
||||
|
||||
|
||||
if (!Storage::exists($path)) {
|
||||
|
||||
|
||||
|
||||
abort(404, 'Datei nicht gefunden');
|
||||
}
|
||||
|
||||
return response()->file(storage_path('app/' . $fullPath), [
|
||||
'Content-Type' => 'application/pdf'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,27 @@
|
||||
|
||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\Invoice\Controllers\ChangeStateController;
|
||||
use App\Domains\Invoice\Controllers\EditController;
|
||||
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
||||
use App\Domains\Invoice\Controllers\ShowInvoiceController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('api/v1/invoice')->group(function () {
|
||||
Route::post('/new/{costUnitId}/{invoiceType}', [NewInvoiceController::class, 'saveInvoice']);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/details/{invoiceId}', ShowInvoiceController::class);
|
||||
Route::get('/showReceipt/{invoiceId}', [ShowInvoiceController::class, 'showReceipt']);
|
||||
|
||||
Route::post('/details/{invoiceId}/change-state/{newState}', ChangeStateController::class);
|
||||
Route::post('/details/{invoiceId}/copy', [EditController::class, 'copyInvoice']);
|
||||
Route::post('/details/{invoiceId}/update', [EditController::class, 'updateInvoice']);
|
||||
|
||||
|
||||
|
||||
|
||||
Route::get('/create', [CreateController::class, 'showForm']);
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,116 @@
|
||||
<script setup>
|
||||
import PdfViewer from "../../../../../Views/Components/PdfViewer.vue";
|
||||
import DistanceAllowance from "./DistanceAllowance.vue";
|
||||
import {onMounted, reactive} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
newInvoice: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
costUnits: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel'])
|
||||
|
||||
const formData = reactive({
|
||||
type_internal: props.newInvoice.internalType || '',
|
||||
cost_unit: props.newInvoice.costUnitId || '',
|
||||
amount: props.newInvoice.amountPlain || '',
|
||||
reason_of_correction: '',
|
||||
})
|
||||
|
||||
const submitForm = () => {
|
||||
emit('submit', formData)
|
||||
}
|
||||
|
||||
|
||||
const invoiceTypeCollection = reactive({
|
||||
invoiceTypes: {}
|
||||
});
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-invoice-types-all');
|
||||
const data = await response.json();
|
||||
Object.assign(invoiceTypeCollection, data);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="submitForm">
|
||||
<table style="width: 100%; font-family: sans-serif;">
|
||||
<tr>
|
||||
<td style="width: 150px;">Rechnungstyp:</td>
|
||||
<td style="width: 450px;">
|
||||
<select v-model="formData.type_internal" class="width-half-full">
|
||||
<option v-for="invoiceType in invoiceTypeCollection.invoiceTypes" :value="invoiceType.slug">{{invoiceType.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Kostenstelle:</td>
|
||||
<td>
|
||||
<select v-model="formData.cost_unit" class="width-half-full">
|
||||
<option v-for="costUnit in props.costUnits" :value="costUnit.id">{{costUnit.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Gesamtbetrag:</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.amount" class="width-small" /> Euro
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Grund der Korrektur:</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.reason_of_correction" class="width-half-full" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="checkbox" v-model="formData.duplicate" id="mareike_correct_invoice_duplicate" />
|
||||
<label for="mareike_correct_invoice_duplicate">Kopie zur Weiterbearbeitung erstellen</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Speichern und freigeben" class="button mareike-accept-button" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<br /><br />
|
||||
|
||||
<PdfViewer :url="'/api/v1/invoice/showReceipt/' + props.newInvoice.id" v-if="props.newInvoice.documentFilename !== null" />
|
||||
<DistanceAllowance v-else :invoice="props.newInvoice" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.width-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.width-almost-full {
|
||||
width: calc(100% - 75px);
|
||||
}
|
||||
|
||||
.width-half-full {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.width-small {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
117
app/Domains/Invoice/Views/Partials/invoiceDetails/Header.vue
Normal file
117
app/Domains/Invoice/Views/Partials/invoiceDetails/Header.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<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">
|
||||
<button
|
||||
v-if="props.data.status === 'new' && modeShow"
|
||||
@click="emit('accept')"
|
||||
class="button mareike-button mareike-accept-button"
|
||||
>
|
||||
Abrechnung annehmen
|
||||
</button>
|
||||
<button v-if="props.data.status === 'denied' && modeShow"
|
||||
@click="emit('reopen')"
|
||||
class="button mareike-button mareike-accept-button"
|
||||
>
|
||||
Abrechnung zur Wiedervorlage öffnen
|
||||
</button>
|
||||
<br />
|
||||
|
||||
<button
|
||||
v-if="props.data.status === 'new' && modeShow"
|
||||
@click="emit('fix')"
|
||||
class="button mareike-button mareike-fix-button"
|
||||
>
|
||||
Abrechnung ablehnen und korrigieren
|
||||
</button><br />
|
||||
|
||||
<button
|
||||
v-if="props.data.status === 'new' && modeShow"
|
||||
@click="emit('deny')"
|
||||
class="button mareike-button mareike-deny-button"
|
||||
>
|
||||
Abrechnung ablehnen
|
||||
</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,285 @@
|
||||
<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 EditInvoicePartial from "./EditInvoice.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 denyInvoice() {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/denied", {
|
||||
method: "POST",
|
||||
body: {
|
||||
reason: document.getElementById('deny_invoice_reason').value
|
||||
}
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success('Abrechnung wurde abgelehnt.');
|
||||
} else {
|
||||
toast.error('Bei der Bearbeitung ist ein Fehler aufgetreten.');
|
||||
|
||||
}
|
||||
|
||||
denyInvoiceDialog.value = false;
|
||||
close();
|
||||
}
|
||||
|
||||
async function reopenInvoice() {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/new", {
|
||||
method: "POST",
|
||||
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success('Die Abrechnung wurde zur erneuten Bearbeitung vorgelegt');
|
||||
} else {
|
||||
toast.error('Beim Bearbeiten ist ein Fehler aufgetreten.');
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<FullScreenModal
|
||||
:show="showInvoice"
|
||||
title="Abrechnungsdetails"
|
||||
@close="emit('close')"
|
||||
>
|
||||
|
||||
<Header :data="props.data"
|
||||
@accept="acceptInvoice"
|
||||
@deny="openDenyInvoiceDialog"
|
||||
@fix="openInvoiceFixDialog"
|
||||
@reopen="reopenInvoice"
|
||||
:modeShow="modeShow"
|
||||
/>
|
||||
|
||||
<ShowInvoicePartial
|
||||
v-if="modeShow"
|
||||
:data="props.data"
|
||||
@accept="acceptInvoice"
|
||||
@deny="openDenyInvoiceDialog"
|
||||
@fix="openInvoiceFixDialog"
|
||||
/>
|
||||
|
||||
<EditInvoicePartial
|
||||
v-else
|
||||
:newInvoice="newInvoice"
|
||||
:costUnits="costUnits"
|
||||
@accept="acceptInvoice"
|
||||
@deny="openDenyInvoiceDialog"
|
||||
@fix="openInvoiceFixDialog"
|
||||
@update="updateInvoice"
|
||||
@submit="updateInvoice"
|
||||
/>
|
||||
|
||||
|
||||
</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,21 @@
|
||||
<script setup>
|
||||
import PdfViewer from "../../../../../Views/Components/PdfViewer.vue";
|
||||
import DistanceAllowance from "./DistanceAllowance.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
console.log(props.data)
|
||||
const emit = defineEmits(["accept", "deny", "fix"])
|
||||
</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>
|
||||
@@ -10,7 +10,10 @@ class InvoiceStatus extends CommonModel
|
||||
public const INVOICE_STATUS_APPROVED = 'approved';
|
||||
public const INVOICE_STATUS_DENIED = 'denied';
|
||||
public const INVOICE_STATUS_EXPORTED = 'exported';
|
||||
public const INVOICE_STAUTS_DELETED = 'deleted';
|
||||
public const INVOICE_STATUS_DELETED = 'deleted';
|
||||
|
||||
public const INVOICE_META_STATUS_NO_PAYOUT = 'no_payout';
|
||||
public const INVOICE_META_STATUS_DONATED = 'donated';
|
||||
|
||||
protected $table = 'invoice_status';
|
||||
|
||||
|
||||
@@ -29,13 +29,13 @@ class CostUnit extends InstancedModel
|
||||
'archived',
|
||||
];
|
||||
|
||||
public function tresurers() : BelongsToMany{
|
||||
public function treasurers() : BelongsToMany{
|
||||
return $this->belongsToMany(User::class, 'cost_unit_treasurers', 'cost_unit_id', 'user_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function resetTreasurers() {
|
||||
$this->tresurers()->detach();
|
||||
$this->treasurers()->detach();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Scopes\InstancedModel;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
@@ -75,4 +76,8 @@ class Invoice extends InstancedModel
|
||||
public function status() : BelongsTo {
|
||||
return $this->belongsTo(InvoiceStatus::class, 'status')->first();
|
||||
}
|
||||
|
||||
public function invoiceType() : InvoiceType {
|
||||
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,28 @@ class GlobalDataProvider {
|
||||
]);
|
||||
}
|
||||
|
||||
public function getAllInvoiceTypes() : JsonResponse {
|
||||
$invoiceTypes = [];
|
||||
foreach (InvoiceType::all() as $invoiceType) {
|
||||
if (
|
||||
$invoiceType->slug === InvoiceType::INVOICE_TYPE_OTHER
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoiceTypes[] = [
|
||||
'slug' => $invoiceType->slug,
|
||||
'name' => $invoiceType->name
|
||||
];
|
||||
}
|
||||
|
||||
$invoiceTypes[] = ['slug' => InvoiceType::INVOICE_TYPE_OTHER, 'name' => 'Sonstige Kosten'];
|
||||
|
||||
return response()->json([
|
||||
'invoiceTypes' => $invoiceTypes
|
||||
]);
|
||||
}
|
||||
|
||||
public function getInvoiceTypes() : JsonResponse {
|
||||
$invoiceTypes = [];
|
||||
foreach (InvoiceType::all() as $invoiceType) {
|
||||
|
||||
@@ -85,7 +85,7 @@ class CostUnitRepository {
|
||||
$visibleCostUnits = [];
|
||||
/** @var CostUnit $costUnit */
|
||||
foreach (Costunit::where($criteria)->get() as $costUnit) {
|
||||
if ($costUnit->tresurers()->where('user_id', $user->id)->exists() || $canSeeAll || $disableAccessCheck) {
|
||||
if ($costUnit->treasurers()->where('user_id', $user->id)->exists() || $canSeeAll || $disableAccessCheck) {
|
||||
if ($forDisplay) {
|
||||
$visibleCostUnits[] = new CostUnitResource($costUnit)->toArray(request());
|
||||
} else {
|
||||
@@ -134,7 +134,7 @@ class CostUnitRepository {
|
||||
return $returnData;
|
||||
}
|
||||
|
||||
public function sumupAmounts(CostUnit $costUnit) : Amount {
|
||||
public function sumupAmounts(CostUnit $costUnit, bool $donatedAmount = false) : Amount {
|
||||
$amount = new Amount(0, '');
|
||||
|
||||
foreach ($costUnit->invoices()->get() as $invoice) {
|
||||
@@ -142,9 +142,57 @@ class CostUnitRepository {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
($donatedAmount && !$invoice->donation) ||
|
||||
(!$donatedAmount && $invoice->donation)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$amount->addAmount(Amount::fromString($invoice->amount));
|
||||
}
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function countInvoices(CostUnit $costUnit) : array {
|
||||
$returnData = [
|
||||
InvoiceStatus::INVOICE_STATUS_NEW => 0,
|
||||
InvoiceStatus::INVOICE_STATUS_APPROVED => 0,
|
||||
InvoiceStatus::INVOICE_STATUS_DENIED => 0,
|
||||
InvoiceStatus::INVOICE_META_STATUS_NO_PAYOUT => 0,
|
||||
InvoiceStatus::INVOICE_META_STATUS_DONATED => 0
|
||||
];
|
||||
|
||||
foreach ($costUnit->invoices()->get() as $invoice) {
|
||||
if ($invoice->status === InvoiceStatus::INVOICE_STATUS_DELETED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if( $invoice->status === InvoiceStatus::INVOICE_STATUS_DENIED) {
|
||||
$returnData[InvoiceStatus::INVOICE_STATUS_DENIED]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($invoice->status) {
|
||||
case InvoiceStatus::INVOICE_STATUS_NEW:
|
||||
$returnData[InvoiceStatus::INVOICE_STATUS_NEW]++;
|
||||
break;
|
||||
case InvoiceStatus::INVOICE_STATUS_APPROVED:
|
||||
$returnData[InvoiceStatus::INVOICE_STATUS_APPROVED]++;
|
||||
break;
|
||||
case InvoiceStatus::INVOICE_STATUS_EXPORTED:
|
||||
if ($invoice->donation) {
|
||||
$returnData[InvoiceStatus::INVOICE_META_STATUS_DONATED]++;
|
||||
} else {
|
||||
if ($invoice->contact_bank_iban === null) {
|
||||
$returnData[InvoiceStatus::INVOICE_META_STATUS_NO_PAYOUT]++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Enumerations\UserRole;
|
||||
use App\Models\CostUnit;
|
||||
use App\Models\Invoice;
|
||||
use App\Resources\InvoiceResource;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class InvoiceRepository {
|
||||
public function getMyInvoicesWidget() : array {
|
||||
@@ -34,4 +38,40 @@ class InvoiceRepository {
|
||||
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
public function getByStatus(CostUnit $costUnit, string $status) : array {
|
||||
$returnData = [];
|
||||
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
||||
$returnData[] = new InvoiceResource($invoice)->toArray();
|
||||
};
|
||||
|
||||
return $returnData;
|
||||
}
|
||||
|
||||
public function getAsTreasurer(int $invoiceId) : ?Invoice {
|
||||
$invoice = Invoice::where('id', $invoiceId)->first();
|
||||
if ($invoice === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$isTreasurer = $invoice->costUnit()->first()->treasurers()->where('user_id', auth()->user()->id)->exists();
|
||||
if ($isTreasurer) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
if ($user->user_role_main === UserRole::USER_ROLE_ADMIN) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
if (app('tenant')->slug === 'lv' && $user->user_role_main === UserRole::USER_ROLE_GROUP_LEADER) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
if (app('tenant')->slug !== 'lv' && $user->local_group === app('tenant')->slug && $user->user_role_local_group === UserRole::USER_ROLE_GROUP_LEADER) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Resources;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Models\CostUnit;
|
||||
use App\Repositories\CostUnitRepository;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class CostUnitResource {
|
||||
@@ -13,13 +15,18 @@ class CostUnitResource {
|
||||
}
|
||||
|
||||
public function toArray($request) {
|
||||
$totalAmount = 0;
|
||||
$donatedAmount = 0;
|
||||
$costUnitRepository = new CostUnitRepository();
|
||||
|
||||
$countNewInvoices = 0;
|
||||
$countApprovedInvoices = 0;
|
||||
$countDonatedInvoices = 0;
|
||||
$countDeniedInvoices = 0;
|
||||
|
||||
$totalAmount = $costUnitRepository->sumupAmounts($this->costUnit)->getAmount();
|
||||
$donatedAmount = $costUnitRepository->sumupAmounts($this->costUnit, true)->getAmount();
|
||||
|
||||
$countInvoices = $costUnitRepository->countInvoices($this->costUnit);
|
||||
|
||||
$countNewInvoices = $countInvoices[InvoiceStatus::INVOICE_STATUS_NEW];
|
||||
$countApprovedInvoices = $countInvoices[InvoiceStatus::INVOICE_STATUS_APPROVED];
|
||||
$countDonatedInvoices = $countInvoices[InvoiceStatus::INVOICE_META_STATUS_DONATED];
|
||||
$countDeniedInvoices = $countInvoices[InvoiceStatus::INVOICE_STATUS_DENIED];
|
||||
|
||||
|
||||
$data = array_merge(
|
||||
@@ -33,7 +40,7 @@ class CostUnitResource {
|
||||
'countApprovedInvoices' => $countApprovedInvoices,
|
||||
'countDonatedInvoices' => $countDonatedInvoices,
|
||||
'countDeniedInvoices' => $countDeniedInvoices,
|
||||
'treasurers' => $this->costUnit->tresurers()->get()->map(fn($user) => new UserResource($user))->toArray(),
|
||||
'treasurers' => $this->costUnit->treasurers()->get()->map(fn($user) => new UserResource($user))->toArray(),
|
||||
]);
|
||||
|
||||
|
||||
|
||||
69
app/Resources/InvoiceResource.php
Normal file
69
app/Resources/InvoiceResource.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Resources;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Models\Invoice;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class InvoiceResource {
|
||||
private Invoice $invoice;
|
||||
|
||||
function __construct(Invoice $invoice) {
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
$returnData = [];
|
||||
$returnData['invoiceType'] = $this->invoice->invoiceType()->name;
|
||||
if ($this->invoice->invoiceType()->slug === InvoiceType::INVOICE_TYPE_OTHER) {
|
||||
$returnData['invoiceType'] .= ' (' . $this->invoice->type_other . ')';
|
||||
}
|
||||
|
||||
$returnData['costUnitName'] = $this->invoice->costUnit()->first()->name;
|
||||
$returnData['invoiceNumber'] = $this->invoice->invoice_number;
|
||||
$returnData['contactName'] = $this->invoice->contact_name;
|
||||
$returnData['contactEmail'] = $this->invoice->contact_email ?? '--';
|
||||
$returnData['contactPhone'] = $this->invoice->contact_phone ?? '--';
|
||||
$returnData['amount'] = Amount::fromString($this->invoice->amount, ' Euro')->toString();
|
||||
$returnData['id'] = $this->invoice->id;
|
||||
$returnData['donation'] = $this->invoice->donation;
|
||||
$returnData['alreadyPaid'] = !$this->invoice->donation && null === $this->invoice->contact_bank_iban;
|
||||
$returnData['accountOwner'] = $this->invoice->contact_bank_owner ?? '--';
|
||||
$returnData['accountIban'] = $this->invoice->contact_bank_iban ?? '--';
|
||||
$returnData['status'] = $this->invoice->status;
|
||||
$returnData['documentFilename'] = $this->invoice->document_filename;
|
||||
$returnData['readableStatus'] = $this->getReadableStatus();
|
||||
$returnData['comment'] = $this->invoice->comment ?? '--';
|
||||
$returnData['changes'] = $this->invoice->changes ?? '--';
|
||||
$returnData['deniedReason'] = $this->invoice->denied_reason ?? '--';
|
||||
$returnData['travelDirection'] = $this->invoice->travel_direction ?? '--';
|
||||
$returnData['distance'] = $this->invoice->distance;
|
||||
$returnData['distanceAllowance'] = new Amount($this->invoice->costUnit()->first()->distance_allowance, '')->toString();
|
||||
$returnData['passengers'] = $this->invoice->passengers ? 'Ja' : 'Nein';
|
||||
$returnData['transportation'] = $this->invoice->transportation ? 'Ja' : 'Nein';
|
||||
$returnData['travelRoute'] = $this->invoice->travel_direction;
|
||||
$returnData['costUnitId'] = $this->invoice->cost_unit_id;
|
||||
$returnData['amountPlain'] = new Amount($this->invoice->amount, '')->toString();
|
||||
$returnData['internalType'] = $this->invoice->type;
|
||||
|
||||
|
||||
return $returnData;
|
||||
}
|
||||
|
||||
private function getReadableStatus() : string {
|
||||
switch ($this->invoice->status) {
|
||||
case InvoiceStatus::INVOICE_STATUS_NEW:
|
||||
return 'Unbearbeitet';
|
||||
case InvoiceStatus::INVOICE_STATUS_APPROVED:
|
||||
return 'Nicht exportiert';
|
||||
case InvoiceStatus::INVOICE_STATUS_DENIED:
|
||||
return 'Abgelehnt';
|
||||
case InvoiceStatus::INVOICE_STATUS_EXPORTED:
|
||||
return 'Exportiert';
|
||||
}
|
||||
return $this->invoice->status;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,4 +40,8 @@ class Amount {
|
||||
public function addAmount(Amount $amount) : void {
|
||||
$this->amount += $amount->getAmount();
|
||||
}
|
||||
|
||||
public function subtractAmount(Amount $amount) : void {
|
||||
$this->amount -= $amount->getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
146
app/Views/Components/FullScreenModal.vue
Normal file
146
app/Views/Components/FullScreenModal.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="show"
|
||||
class="full-screen-modal-overlay"
|
||||
@click.self="close"
|
||||
>
|
||||
<transition name="scale">
|
||||
<div
|
||||
v-if="show"
|
||||
ref="modalRef"
|
||||
class="full-screen-modal-content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="full-screen-modal-body">
|
||||
<slot />
|
||||
<span @click="close" class="dashicons dashicons-dismiss full-screen-modal-close"></span>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const modalRef = ref(null)
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// ESC-Key & Focus-Trap
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Escape') {
|
||||
close()
|
||||
}
|
||||
if (e.key === 'Tab' && modalRef.value) {
|
||||
const focusable = modalRef.value.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
if (focusable.length === 0) return
|
||||
|
||||
const first = focusable[0]
|
||||
const last = focusable[focusable.length - 1]
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault()
|
||||
last.focus()
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault()
|
||||
first.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Body-Scroll sperren
|
||||
watch(
|
||||
() => props.show,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
await nextTick()
|
||||
modalRef.value?.focus()
|
||||
} else {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.full-screen-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.full-screen-modal-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
bottom: 30px;
|
||||
left: 30px;
|
||||
right: 30px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.full-screen-modal-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.full-screen-modal-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
.scale-enter-from,
|
||||
.scale-leave-to {
|
||||
transform: scale(0.98);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -7,8 +7,17 @@ const props = defineProps({
|
||||
name: { type: String, required: true },
|
||||
})
|
||||
|
||||
if (SolidIcons[`fa${props.name.charAt(0).toUpperCase()}${props.name.slice(1)}`]) {
|
||||
library.add(SolidIcons[`fa${props.name.charAt(0).toUpperCase()}${props.name.slice(1)}`])
|
||||
function toPascalCase(str) {
|
||||
return str
|
||||
.split('-')
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join('')
|
||||
}
|
||||
|
||||
const iconName = `fa${toPascalCase(props.name)}`
|
||||
|
||||
if (SolidIcons[iconName]) {
|
||||
library.add(SolidIcons[iconName])
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
46
app/Views/Components/PdfViewer.vue
Normal file
46
app/Views/Components/PdfViewer.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
import { VuePDF, usePDF } from '@tato30/vue-pdf'
|
||||
|
||||
const props = defineProps({
|
||||
url: String
|
||||
})
|
||||
|
||||
|
||||
|
||||
const page = ref(1)
|
||||
const { pdf, pages } = usePDF(props.url)
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="mareike-invoice-pdfview-controls">
|
||||
<button @click="page = page > 1 ? page - 1 : page" class="button mareike-button page-switch-button">
|
||||
Vorherige Seite
|
||||
</button>
|
||||
<span>{{ page }} / {{ pages }}</span>
|
||||
|
||||
<button @click="page = page < pages ? page + 1 : page" class="button mareike-button page-switch-button">
|
||||
Nächste Seite
|
||||
</button>
|
||||
|
||||
<VuePDF :pdf="pdf" :page="page" class="mareike_pdv_view" fitParent="true" cale="0.9" height="90"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-switch-button {
|
||||
padding: 8px 20px !important;
|
||||
width: 200px !important;
|
||||
margin: 0 10px !important;
|
||||
}
|
||||
|
||||
.mareike-invoice-pdfview-controls {
|
||||
position: relative;
|
||||
top: -50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -25,6 +25,7 @@
|
||||
"@inertiajs/progress": "^0.2.7",
|
||||
"@inertiajs/vue3": "^2.3.12",
|
||||
"vue": "^3.5.27",
|
||||
"vue3-toastify": "^0.2.8"
|
||||
"vue3-toastify": "^0.2.8",
|
||||
"@tato30/vue-pdf": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ th:after {
|
||||
.widget-content-item td,
|
||||
.widget-content-item th {
|
||||
font-size: 10pt;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.widget-content-item label {
|
||||
|
||||
@@ -66,5 +66,6 @@ input[type="submit"]:hover {
|
||||
.link {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: #47a0d8;
|
||||
font-weight: bold;
|
||||
color: #2a7caf;
|
||||
}
|
||||
|
||||
@@ -39,3 +39,27 @@ fieldset legend {
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
.invoice-list-table {
|
||||
width: 90%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.invoice-list-table td {
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.invoice-list-table tr td:first-child {
|
||||
border-left: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
.invoice-list-table tr td:last-child {
|
||||
border-right: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
|
||||
.invoice-list-table tr:first-child td {
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::get('/retrieve-text-resource/{textResource}', [GlobalDataProvider::class, 'getTextResourceText']);
|
||||
Route::get('/retrieve-messages', [GlobalDataProvider::class, 'getMessages']);
|
||||
Route::get('/retrieve-invoice-types', [GlobalDataProvider::class, 'getInvoiceTypes']);
|
||||
Route::get('/retrieve-invoice-types-all', [GlobalDataProvider::class, 'getAllInvoiceTypes']);
|
||||
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user