Invoice PAIN & CSV can be uploaded
This commit is contained in:
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
110
app/Domains/CostUnit/Controllers/ExportController.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\CostUnit\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||||
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||||
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Providers\FileWriteProvider;
|
||||||
|
use App\Providers\InvoiceCsvFileProvider;
|
||||||
|
use App\Providers\PainFileProvider;
|
||||||
|
use App\Providers\WebDavProvider;
|
||||||
|
use App\Providers\ZipArchiveFileProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ExportController extends CommonController {
|
||||||
|
public function __invoke(int $costUnitId) {
|
||||||
|
$costUnit = $this->costUnits->getById($costUnitId);
|
||||||
|
|
||||||
|
$invoicesForExport = $this->invoices->getByStatus($costUnit, InvoiceStatus::INVOICE_STATUS_APPROVED, false);
|
||||||
|
|
||||||
|
$webdavProvider = new WebDavProvider(WebDavProvider::INVOICE_PREFIX . $this->tenant->url . '/' . $costUnit->name);
|
||||||
|
|
||||||
|
$painFileData = $this->painData($invoicesForExport);
|
||||||
|
$csvData = $this->csvData($invoicesForExport);
|
||||||
|
|
||||||
|
$filePrefix = Tenant::getTempDirectory();
|
||||||
|
|
||||||
|
$painFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '-sepa.xml', $painFileData);
|
||||||
|
$painFileWriteProvider->writeToFile();
|
||||||
|
|
||||||
|
$csvFileWriteProvider = new FileWriteProvider($filePrefix . 'abrechnungen-' . date('Y-m-d_H-i') . '.csv', $csvData);
|
||||||
|
$csvFileWriteProvider->writeToFile();
|
||||||
|
|
||||||
|
if ($this->tenant->upload_exports) {
|
||||||
|
$webdavProvider->uploadFile($painFileWriteProvider->fileName);
|
||||||
|
$webdavProvider->uploadFile($csvFileWriteProvider->fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadZipArchiveFiles = [
|
||||||
|
$painFileWriteProvider->fileName,
|
||||||
|
$csvFileWriteProvider->fileName
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($invoicesForExport as $invoice) {
|
||||||
|
$changeStatusRequest = new ChangeStatusRequest($invoice, InvoiceStatus::INVOICE_STATUS_EXPORTED);
|
||||||
|
$changeStatusCommand = new ChangeStatusCommand($changeStatusRequest);
|
||||||
|
$changeStatusCommand->execute();
|
||||||
|
|
||||||
|
if ($this->tenant->download_exports) {
|
||||||
|
$createInvoiceReceiptRequest = new CreateInvoiceReceiptRequest($invoice);
|
||||||
|
$createInvoiceReceiptCommand = new CreateInvoiceReceiptCommand($createInvoiceReceiptRequest);
|
||||||
|
$response = $createInvoiceReceiptCommand->execute();
|
||||||
|
|
||||||
|
$downloadZipArchiveFiles[] = $response->fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->tenant->download_exports) {
|
||||||
|
$zipFile = Tenant::getTempDirectory() . 'Abrechnungen-' . $costUnit->name . '.zip';
|
||||||
|
$zipFileProvider = new ZipArchiveFileProvider($zipFile);
|
||||||
|
foreach ($downloadZipArchiveFiles as $file) {
|
||||||
|
$zipFileProvider->addFile($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zipFileProvider->create();
|
||||||
|
foreach ($downloadZipArchiveFiles as $file) {
|
||||||
|
Storage::delete($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::delete($painFileWriteProvider->fileName);
|
||||||
|
Storage::delete($csvFileWriteProvider->fileName);
|
||||||
|
|
||||||
|
return response()->download(
|
||||||
|
storage_path('app/private/' . $zipFile),
|
||||||
|
basename($zipFile),
|
||||||
|
['Content-Type' => 'application/zip']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::delete($painFileWriteProvider->fileName);
|
||||||
|
Storage::delete($csvFileWriteProvider->fileName);
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Die Abrechnungen wurden exportiert.' . PHP_EOL .'Die Belege werden asynchron auf dem Webdav-Server hinterlegt.' . PHP_EOL . PHP_EOL . 'Sollten diese in 15 Minuten nicht vollständig sein, kontaktiere den Adminbistrator.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function painData(array $invoices) : string {
|
||||||
|
$invoicesForPainFile = [];
|
||||||
|
foreach ($invoices as $invoice) {
|
||||||
|
if ($invoice->contact_bank_owner !== null && $invoice->contact_bank_iban !== '' && !$invoice->donation) {
|
||||||
|
$invoicesForPainFile[] = $invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$painFileProvider = new PainFileProvider($this->tenant->account_iban, $this->tenant->account_name, $this->tenant->account_bic, $invoicesForPainFile);
|
||||||
|
return $painFileProvider->createPainFileContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function csvData(array $invoices) : string {
|
||||||
|
$csvDateProvider = new InvoiceCsvFileProvider($invoices);
|
||||||
|
return $csvDateProvider->createCsvFileContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ use App\Domains\CostUnit\Controllers\ChangeStateController;
|
|||||||
use App\Domains\CostUnit\Controllers\CreateController;
|
use App\Domains\CostUnit\Controllers\CreateController;
|
||||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||||
use App\Domains\CostUnit\Controllers\EditController;
|
use App\Domains\CostUnit\Controllers\EditController;
|
||||||
|
use App\Domains\CostUnit\Controllers\ExportController;
|
||||||
use App\Domains\CostUnit\Controllers\ListController;
|
use App\Domains\CostUnit\Controllers\ListController;
|
||||||
use App\Domains\CostUnit\Controllers\OpenController;
|
use App\Domains\CostUnit\Controllers\OpenController;
|
||||||
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
||||||
@@ -37,6 +38,7 @@ Route::prefix('api/v1')
|
|||||||
Route::get('/treasurers', TreasurersEditController::class);
|
Route::get('/treasurers', TreasurersEditController::class);
|
||||||
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
||||||
|
|
||||||
|
Route::get('/export-payouts', ExportController::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {createApp, ref} from 'vue'
|
import {createApp, ref} from 'vue'
|
||||||
/*import {
|
import LoadingModal from "../../../../Views/Components/LoadingModal.vue";
|
||||||
_mareike_download_as_zip,
|
|
||||||
|
|
||||||
_mareike_use_webdav
|
|
||||||
} from "../../../assets/javascripts/library";*/
|
|
||||||
//import LoadingModal from "../../../assets/components/LoadingModal.vue";
|
|
||||||
//import Invoices from '../invoices/index.vue'
|
|
||||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
import CostUnitDetails from "./CostUnitDetails.vue";
|
import CostUnitDetails from "./CostUnitDetails.vue";
|
||||||
import {toast} from "vue3-toastify";
|
import {toast} from "vue3-toastify";
|
||||||
import Treasurers from "./Treasurers.vue";
|
import Treasurers from "./Treasurers.vue";
|
||||||
|
|
||||||
//import CostUnitDetails from "./CostUnitDetails.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: [Array, Object],
|
type: [Array, Object],
|
||||||
@@ -42,13 +34,8 @@ const show_cost_unit = ref(false)
|
|||||||
const showTreasurers = ref(false)
|
const showTreasurers = ref(false)
|
||||||
const costUnit = ref(null)
|
const costUnit = ref(null)
|
||||||
|
|
||||||
|
|
||||||
const { data, loading, error, request, download } = useAjax()
|
const { data, loading, error, request, download } = useAjax()
|
||||||
|
|
||||||
if (props.deep_jump_id > 0) {
|
|
||||||
// open_invoice_list(props.deep_jump_id, 'new', props.deep_jump_id_sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function costUnitDetails(costUnitId) {
|
async function costUnitDetails(costUnitId) {
|
||||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -114,41 +101,46 @@ async function changeCostUnitState(costUnitId, endPoint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function export_payouts(cost_unit_id) {
|
async function exportPayouts(costUnitId) {
|
||||||
showLoading.value = true;
|
showLoading.value = true;
|
||||||
|
|
||||||
|
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||||
|
const data = await response.json();
|
||||||
|
const exportUrl = '/api/v1/cost-unit/' + costUnitId + '/export-payouts';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (_mareike_download_as_zip()) {
|
if (data.tenant.download_exports) {
|
||||||
const response = await fetch("/wp-json/mareike/costunits/export-payouts", {
|
const response = await fetch(exportUrl, {
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
|
||||||
mareike_nonce: _mareike_nonce(),
|
|
||||||
costunit: cost_unit_id,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
||||||
|
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const downloadUrl = window.URL.createObjectURL(blob);
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
console.log(response.headers.get("content-type"));
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
a.href = downloadUrl;
|
a.href = downloadUrl;
|
||||||
a.download = `payouts-${cost_unit_id}.zip`;
|
a.download = "Abrechnungen-Sippenstunden.zip";
|
||||||
|
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
|
||||||
|
setTimeout(() => {
|
||||||
window.URL.revokeObjectURL(downloadUrl);
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
await request("/wp-json/mareike/costunits/export-payouts", {
|
const response = await request(exportUrl, {
|
||||||
method: "POST",
|
method: "GET",
|
||||||
body: {
|
|
||||||
mareike_nonce: _mareike_nonce(),
|
|
||||||
costunit: cost_unit_id,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
toast.success(response.message);
|
||||||
}
|
}
|
||||||
showLoading.value = false;
|
showLoading.value = false;
|
||||||
toast.success('Die Abrechnungen wurden exportiert.');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showLoading.value = false;
|
showLoading.value = false;
|
||||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||||
@@ -176,7 +168,7 @@ async function export_payouts(cost_unit_id) {
|
|||||||
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
||||||
<input v-else type="button" value="Abrechnungen einsehen" />
|
<input v-else type="button" value="Abrechnungen einsehen" />
|
||||||
<br />
|
<br />
|
||||||
<input v-if="!costUnit.archived" type="button" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
<input v-if="!costUnit.archived" type="button" @click="exportPayouts(costUnit.id)" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@@ -217,17 +209,10 @@ async function export_payouts(cost_unit_id) {
|
|||||||
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div v-else-if="showInvoiceList">
|
<div v-else-if="showInvoiceList">
|
||||||
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
||||||
|
|
||||||
|
|
||||||
<LoadingModal :show="showLoading" v-if="_mareike_use_webdav()" message="Die PDF-Dateien werden asynchron erzeugt, diese sollten in 10 Minuten auf dem Webdav-Server liegen', 'mareike')" />
|
|
||||||
<LoadingModal :show="showLoading" v-else message='Die Abrechnungen werden exportiert, bitte warten.' />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@@ -236,7 +221,7 @@ async function export_payouts(cost_unit_id) {
|
|||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LoadingModal :show="showLoading" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class ChangeStatusCommand {
|
|||||||
case InvoiceStatus::INVOICE_STATUS_DELETED:
|
case InvoiceStatus::INVOICE_STATUS_DELETED:
|
||||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DELETED;
|
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DELETED;
|
||||||
break;
|
break;
|
||||||
|
case InvoiceStatus::INVOICE_STATUS_EXPORTED:
|
||||||
|
//$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_EXPORTED;
|
||||||
|
$this->request->invoice->upload_required = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->invoice->save()) {
|
if ($this->request->invoice->save()) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Invoice\Actions\ChangeStatus;
|
|||||||
|
|
||||||
class ChangeStatusResponse {
|
class ChangeStatusResponse {
|
||||||
public bool $success;
|
public bool $success;
|
||||||
|
public string $invoiceReceipt;
|
||||||
|
|
||||||
function __construct() {
|
function __construct() {
|
||||||
$this->success = false;
|
$this->success = false;
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\CreateInvoiceReceipt;
|
||||||
|
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
|
use App\Models\PageText;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Providers\PdfMergeProvider;
|
||||||
|
use App\Resources\InvoiceResource;
|
||||||
|
use Dompdf\Dompdf;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class CreateInvoiceReceiptCommand {
|
||||||
|
private CreateInvoiceReceiptRequest $request;
|
||||||
|
private string $tempDirectory;
|
||||||
|
|
||||||
|
public function __construct(CreateInvoiceReceiptRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
$this->tempDirectory = Tenant::getTempDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : CreateInvoiceReceiptResponse {
|
||||||
|
$response = new CreateInvoiceReceiptResponse();
|
||||||
|
|
||||||
|
$filename = $this->tempDirectory . $this->request->invoice->invoice_number . '.pdf';
|
||||||
|
if (!Storage::exists($this->tempDirectory)) {
|
||||||
|
Storage::makeDirectory(Tenant::getTempDirectory() . '/' . $this->tempDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
$receipt = $this->request->invoice->document_filename;
|
||||||
|
if ($receipt === null) {
|
||||||
|
Storage::put(
|
||||||
|
$filename,
|
||||||
|
$this->createPdf( $this->createHtml(), 'portrait', $filename, false )
|
||||||
|
);
|
||||||
|
|
||||||
|
$response->fileName = $filename;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = (string)rand(1000000000, 9999999999);
|
||||||
|
$pdf_data = $this->createPdf( $this->createHtml(), 'portrait', $filename, false );
|
||||||
|
|
||||||
|
$tmpFileName = $this->tempDirectory . 'tmp-' . $token . '.pdf';
|
||||||
|
Storage::put($tmpFileName, $pdf_data);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$merger = new PdfMergeProvider();
|
||||||
|
|
||||||
|
$merger
|
||||||
|
->add( storage_path('app/private/' . $tmpFileName ))
|
||||||
|
->add(storage_path('app/private/' . $receipt))
|
||||||
|
->merge(storage_path('app/private/' .$filename) );
|
||||||
|
|
||||||
|
$response->fileName = $filename;
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$zip->open(
|
||||||
|
storage_path('app/private/' .$filename) . '.zip',
|
||||||
|
ZipArchive::CREATE | ZipArchive::OVERWRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
$zip->addFile(storage_path('app/private/' . $tmpFileName ), 'antrag.pdf');
|
||||||
|
$zip->addFile(storage_path('app/private/' . $receipt), 'beleg.pdf');
|
||||||
|
|
||||||
|
$zip->close();
|
||||||
|
$response->fileName = $filename . '.zip';
|
||||||
|
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
Storage::delete($tmpFileName);
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private function createHtml() : string {
|
||||||
|
|
||||||
|
|
||||||
|
$invoiceReadable = new InvoiceResource($this->request->invoice)->toArray();
|
||||||
|
|
||||||
|
|
||||||
|
$travelPartTemplate = <<<HTML
|
||||||
|
<tr><td>Reiseweg:</td><td>%1\$s</td></tr>
|
||||||
|
<tr><td>Gesamtlänge der Strecke:</td><td>%2\$s km x %3\$s / km</td></tr>
|
||||||
|
<tr><td>Materialtransport:</td><td>%4\$s</td></tr>
|
||||||
|
<tr><td>Mitfahrende im PKW:</td><td>%5\$s</td></tr>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
$flatTravelPart = sprintf(
|
||||||
|
$travelPartTemplate,
|
||||||
|
$invoiceReadable['travelDirection'] ,
|
||||||
|
$invoiceReadable['distance'],
|
||||||
|
$invoiceReadable['distanceAllowance'],
|
||||||
|
$invoiceReadable['transportation'],
|
||||||
|
$invoiceReadable['passengers']
|
||||||
|
);
|
||||||
|
|
||||||
|
$invoiceTravelPart = '<tr><td>Kosten für ÖPNV:</td><td>' . $invoiceReadable['amount'] . '</td></tr>';;
|
||||||
|
$expensePart = '<tr><td>Auslagenerstattung:</td><td>' . $invoiceReadable['amount'] . '</td></tr>';
|
||||||
|
|
||||||
|
$content = <<<HTML
|
||||||
|
<html>
|
||||||
|
<body style="margin-left: 20mm; margin-top: 17mm">
|
||||||
|
<h3>Abrechnungstyp %1\$s</h3><br /><br />
|
||||||
|
|
||||||
|
|
||||||
|
<table style="width: 100%%;">
|
||||||
|
<tr><td>Abrechnungsnummer:</td><td>%2\$s</td></tr>
|
||||||
|
<tr><td>Name:</td><td>%3\$s</td></tr>
|
||||||
|
<tr><td>E-Mail:</td><td>%4\$s</td></tr>
|
||||||
|
<tr><td>Telefon:</td><td>%5\$s</td></tr>
|
||||||
|
<tr><td>Name der Kostenstelle:</td><td>%6\$s</td></tr>
|
||||||
|
<tr><td>Zahlungsgrund:</td><td>%7\$s</td></tr>
|
||||||
|
<tr><td>Wird der Betrag gespendet:</td><td>%8\$s</td></tr>
|
||||||
|
%9\$s
|
||||||
|
<tr style="font-weight: bold;">
|
||||||
|
<td style="border-bottom-width: 1px; border-bottom-style: double;">
|
||||||
|
Gesamtbetrag:
|
||||||
|
</td>
|
||||||
|
<td style="border-bottom-width: 1px; border-bottom-style: double;">
|
||||||
|
%10\$s
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td colspan="2"><br /><br /></td></tr>
|
||||||
|
%11\$s
|
||||||
|
<tr><td>Beleg digital eingereicht am:</td><td>%12\$s</td></tr>
|
||||||
|
<tr><td>Beleg akzeptiert am:</td><td>%13\$s</td></tr>
|
||||||
|
<tr><td>Beleg akzeptiert von:</td><td>%14\$s</td></tr>
|
||||||
|
</table>
|
||||||
|
%15\$s
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
switch ($this->request->invoice->type) {
|
||||||
|
case InvoiceType::INVOICE_TYPE_TRAVELLING:
|
||||||
|
$paymentType = $this->request->invoice->distance !== null ? $flatTravelPart : $invoiceTravelPart;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$paymentType = $expensePart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->invoice->donation) {
|
||||||
|
$paymentInformation = '<tr><td colspan="2">' . PageText::where('name', 'CONFIRMATION_DONATE')->first()->content . '</td></tr>';
|
||||||
|
} else {
|
||||||
|
if ($this->request->invoice->contact_bank_iban === null) {
|
||||||
|
$paymentInformation = '';
|
||||||
|
} else {
|
||||||
|
$paymentInformationTemplate = <<<HTML
|
||||||
|
<tr><td colspan="2">%1\$s</td></tr>
|
||||||
|
<tr><td>Kontoinhaber*in:</td><td>%2\$s</td></tr>
|
||||||
|
<tr><td>INAN:</td><td>%3\$s</td></tr>
|
||||||
|
<tr><td colspan="2"><br /><br /></td></tr>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
$paymentInformation = sprintf(
|
||||||
|
$paymentInformationTemplate,
|
||||||
|
PageText::where('name', 'CONFIRMATION_PAYMENT')->first()->content,
|
||||||
|
$invoiceReadable['accountOwner'],
|
||||||
|
$invoiceReadable['accountIban']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$changes = $this->request->invoice->changes !== null ? '<p>' . $this->request->invoice->changes . '</p>' : '';
|
||||||
|
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
$content,
|
||||||
|
$invoiceReadable['invoiceTypeShort'],
|
||||||
|
$invoiceReadable['invoiceNumber'],
|
||||||
|
$invoiceReadable['contactName'],
|
||||||
|
$invoiceReadable['contactEmail'],
|
||||||
|
$invoiceReadable['contactPhone'],
|
||||||
|
$invoiceReadable['costUnitName'],
|
||||||
|
$invoiceReadable['invoiceType'],
|
||||||
|
$invoiceReadable['donationText'],
|
||||||
|
$paymentType,
|
||||||
|
$invoiceReadable['amount'],
|
||||||
|
$paymentInformation,
|
||||||
|
$invoiceReadable['createdAt'],
|
||||||
|
$invoiceReadable['approvedAt'],
|
||||||
|
$invoiceReadable['approvedBy'],
|
||||||
|
$changes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createPdf( string $htmlfile, string $orientation, string $filename, bool $download = true ) {
|
||||||
|
|
||||||
|
$dompdf = new Dompdf();
|
||||||
|
$dompdf->loadHtml( $htmlfile, 'UTF-8' );
|
||||||
|
$dompdf->setPaper( 'A4', $orientation );
|
||||||
|
|
||||||
|
$dompdf->render();
|
||||||
|
if ( ! $download ) {
|
||||||
|
return $dompdf->output();
|
||||||
|
}
|
||||||
|
$dompdf->stream( $filename );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\CreateInvoiceReceipt;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
|
||||||
|
class CreateInvoiceReceiptRequest {
|
||||||
|
public Invoice $invoice;
|
||||||
|
|
||||||
|
public function __construct(Invoice $invoice) {
|
||||||
|
$this->invoice = $invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\CreateInvoiceReceipt;
|
||||||
|
|
||||||
|
class CreateInvoiceReceiptResponse {
|
||||||
|
public string $fileName;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->fileName = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
|||||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
|
||||||
class UpdateInvoiceCommand {
|
class UpdateInvoiceCommand {
|
||||||
private UpdateInvoiceRequest $request;
|
private UpdateInvoiceRequest $request;
|
||||||
@@ -15,11 +16,26 @@ class UpdateInvoiceCommand {
|
|||||||
|
|
||||||
public function execute() : UpdateInvoiceResponse {
|
public function execute() : UpdateInvoiceResponse {
|
||||||
$response = new UpdateInvoiceResponse();
|
$response = new UpdateInvoiceResponse();
|
||||||
|
$changes = $this->request->invoice->changes ?? '';
|
||||||
|
|
||||||
|
if ($this->request->invoice->amount !== $this->request->amount->getAmount()) {
|
||||||
|
$changes .= 'Betrag geändert von ' . Amount::fromString($this->request->invoice->amount)->toString() . ' auf ' . Amount::fromString($this->request->amount->getAmount())->toString() . '.<br />';
|
||||||
$this->request->invoice->amount = $this->request->amount->getAmount();
|
$this->request->invoice->amount = $this->request->amount->getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->invoice->invoiceType()->slug !== $this->request->invoiceType->slug) {
|
||||||
|
$changes .= 'Abrechnungstyp geändert von ' . $this->request->invoice->invoiceType()->name . ' auf ' . $this->request->invoiceType->name . '.<br />';
|
||||||
|
$this->request->invoice->type = $this->request->invoiceType->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->invoice->costUnit()->first()->id !== $this->request->costUnit->id) {
|
||||||
|
$changes .= 'Kostenstelle geändert von ' . $this->request->invoice->costUnit()->first()->name . ' auf ' . $this->request->costUnit->name . '.<br />';
|
||||||
$this->request->invoice->cost_unit_id = $this->request->costUnit->id;
|
$this->request->invoice->cost_unit_id = $this->request->costUnit->id;
|
||||||
$this->request->invoice->type = $this->request->invoiceType;
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->request->invoice->comment = $this->request->comment;
|
$this->request->invoice->comment = $this->request->comment;
|
||||||
|
$this->request->invoice->changes = $changes;
|
||||||
|
|
||||||
$this->request->invoice->save();
|
$this->request->invoice->save();
|
||||||
|
|
||||||
@@ -29,5 +45,4 @@ class UpdateInvoiceCommand {
|
|||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ use App\ValueObjects\Amount;
|
|||||||
|
|
||||||
class UpdateInvoiceRequest {
|
class UpdateInvoiceRequest {
|
||||||
public string $comment;
|
public string $comment;
|
||||||
public string $invoiceType;
|
public InvoiceType $invoiceType;
|
||||||
public CostUnit $costUnit;
|
public CostUnit $costUnit;
|
||||||
public Invoice $invoice;
|
public Invoice $invoice;
|
||||||
public Amount $amount;
|
public Amount $amount;
|
||||||
|
|
||||||
public function __construct(Invoice $invoice, string $comment, string $invoiceType, CostUnit $costUnit, Amount $amount) {
|
public function __construct(Invoice $invoice, string $comment, InvoiceType $invoiceType, CostUnit $costUnit, Amount $amount) {
|
||||||
$this->comment = $comment;
|
$this->comment = $comment;
|
||||||
$this->invoiceType = $invoiceType;
|
$this->invoiceType = $invoiceType;
|
||||||
$this->costUnit = $costUnit;
|
$this->costUnit = $costUnit;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceCommand;
|
|||||||
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceRequest;
|
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceRequest;
|
||||||
use App\Enumerations\CostUnitType;
|
use App\Enumerations\CostUnitType;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
|
use App\Enumerations\InvoiceType;
|
||||||
use App\Resources\InvoiceResource;
|
use App\Resources\InvoiceResource;
|
||||||
use App\Scopes\CommonController;
|
use App\Scopes\CommonController;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
@@ -80,10 +81,13 @@ class EditController extends CommonController{
|
|||||||
$amountLeft->subtractAmount($newAmount);
|
$amountLeft->subtractAmount($newAmount);
|
||||||
|
|
||||||
$newCostUnit = $this->costUnits->getById($modifyData['cost_unit'],true);
|
$newCostUnit = $this->costUnits->getById($modifyData['cost_unit'],true);
|
||||||
|
$invoiceType = InvoiceType::where('slug', $modifyData['type_internal'])->first();
|
||||||
|
|
||||||
|
|
||||||
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
||||||
$invoice,
|
$invoice,
|
||||||
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
||||||
$modifyData['type_internal'],
|
$invoiceType,
|
||||||
$newCostUnit,
|
$newCostUnit,
|
||||||
$newAmount
|
$newAmount
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ class DevelopmentDataSeeder {
|
|||||||
'slug' => 'wilde-moehre',
|
'slug' => 'wilde-moehre',
|
||||||
'name' => 'Stamm Wilde Möhre',
|
'name' => 'Stamm Wilde Möhre',
|
||||||
'url' => 'wilde-moehre.mareike.local',
|
'url' => 'wilde-moehre.mareike.local',
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V. - Stamm Wilde Möhre',
|
||||||
'account_iban' => 'DE12345678901234567890',
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
'email' => 'test@example1.com',
|
'email' => 'test@example1.com',
|
||||||
'city' => 'Halle (Saale)',
|
'city' => 'Halle (Saale)',
|
||||||
'postcode' => '06120',
|
'postcode' => '06120',
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ class ProductionDataSeeder {
|
|||||||
'slug' => 'lv',
|
'slug' => 'lv',
|
||||||
'name' => 'Landesunmittelbare Mitglieder',
|
'name' => 'Landesunmittelbare Mitglieder',
|
||||||
'url' => 'mareike.local',
|
'url' => 'mareike.local',
|
||||||
|
'account_name' => 'Bund der Pfadfinder*innen Landesverband Sachsen e.V.',
|
||||||
'account_iban' => 'DE12345678901234567890',
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'account_bic' => 'GENODEF1S10',
|
||||||
'email' => 'test@example.com',
|
'email' => 'test@example.com',
|
||||||
'city' => 'Lommatzsch',
|
'city' => 'Lommatzsch',
|
||||||
'postcode' => '01623',
|
'postcode' => '01623',
|
||||||
|
|||||||
@@ -80,4 +80,8 @@ class Invoice extends InstancedModel
|
|||||||
public function invoiceType() : InvoiceType {
|
public function invoiceType() : InvoiceType {
|
||||||
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
return $this->belongsTo(InvoiceType::class, 'type', 'slug')->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function approvedBy() : ?User {
|
||||||
|
return $this->belongsTo(User::class, 'approved_by')->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ use App\Scopes\CommonModel;
|
|||||||
* @property string $local_group
|
* @property string $local_group
|
||||||
* @property string $email
|
* @property string $email
|
||||||
* @property string $url
|
* @property string $url
|
||||||
|
* @property string $account_name
|
||||||
* @property string $account_iban
|
* @property string $account_iban
|
||||||
|
* @property string $account_bic
|
||||||
* @property string $city
|
* @property string $city
|
||||||
* @property string $postcode
|
* @property string $postcode
|
||||||
|
* @property boolean $download_exports
|
||||||
|
* @property boolean $upload_exports
|
||||||
* @property string $gdpr_text
|
* @property string $gdpr_text
|
||||||
* @property string $impress_text
|
* @property string $impress_text
|
||||||
* @property string $url_participation_rules
|
* @property string $url_participation_rules
|
||||||
@@ -20,6 +24,9 @@ use App\Scopes\CommonModel;
|
|||||||
*/
|
*/
|
||||||
class Tenant extends CommonModel
|
class Tenant extends CommonModel
|
||||||
{
|
{
|
||||||
|
public static function getTempDirectory() : string {
|
||||||
|
return app('tenant')->slug . '/temp-data/';
|
||||||
|
}
|
||||||
public const PRIMARY_TENANT_NAME = 'LV';
|
public const PRIMARY_TENANT_NAME = 'LV';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -27,9 +34,13 @@ class Tenant extends CommonModel
|
|||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'url',
|
'url',
|
||||||
|
'account_name',
|
||||||
'account_iban',
|
'account_iban',
|
||||||
|
'account_bic',
|
||||||
'city',
|
'city',
|
||||||
'postcode',
|
'postcode',
|
||||||
|
'download_exports',
|
||||||
|
'upload_exports',
|
||||||
'gdpr_text',
|
'gdpr_text',
|
||||||
'impress_text',
|
'impress_text',
|
||||||
'url_participation_rules',
|
'url_participation_rules',
|
||||||
|
|||||||
19
app/Providers/FileWriteProvider.php
Normal file
19
app/Providers/FileWriteProvider.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class FileWriteProvider {
|
||||||
|
public string $fileName;
|
||||||
|
public string $fileContent;
|
||||||
|
|
||||||
|
public function __construct(string $fileName, string $fileContent) {
|
||||||
|
$this->fileName = $fileName;
|
||||||
|
$this->fileContent = $fileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeToFile() {
|
||||||
|
Storage::disk('local')->put($this->fileName, $this->fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/Providers/InvoiceCsvFileProvider.php
Normal file
49
app/Providers/InvoiceCsvFileProvider.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Resources\InvoiceResource;
|
||||||
|
|
||||||
|
class InvoiceCsvFileProvider {
|
||||||
|
|
||||||
|
/* @var Invoice[] */
|
||||||
|
private array $invoices;
|
||||||
|
|
||||||
|
public function __construct(array $invoices) {
|
||||||
|
$this->invoices = $invoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createCsvFileContent() : string
|
||||||
|
{
|
||||||
|
$csvArray[] = implode(
|
||||||
|
',', [
|
||||||
|
'"Rechnungsnummer"',
|
||||||
|
'"Zahlungsgrund"',
|
||||||
|
'"Name"',
|
||||||
|
'"IBAN"',
|
||||||
|
'"Kontoinhaber*in"',
|
||||||
|
'"Betrag"',
|
||||||
|
'"Spende"',
|
||||||
|
'"Beleg ohne Auszahlung"',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($this->invoices as $invoice) {
|
||||||
|
$invoiceReadable = new InvoiceResource($invoice)->toArray();
|
||||||
|
$csvLine = implode(',', [
|
||||||
|
'"' . $invoiceReadable['invoiceNumber'] . '"',
|
||||||
|
'"' . $invoiceReadable['invoiceType'] . '"',
|
||||||
|
'"' . $invoiceReadable['contactName'] . '"',
|
||||||
|
'"' . $invoiceReadable['accountIban'] . '"',
|
||||||
|
'"' . $invoiceReadable['accountOwner'] . '"',
|
||||||
|
'"' . $invoiceReadable['amountPlain'] . '"',
|
||||||
|
'"' . $invoiceReadable['donation'] . '"',
|
||||||
|
'"' . $invoiceReadable['alreadyPaid'] . '"'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$csvArray[] = $csvLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(PHP_EOL, $csvArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
app/Providers/PainFileProvider.php
Normal file
127
app/Providers/PainFileProvider.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use DOMDocument;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PainFileProvider {
|
||||||
|
public string $senderIban;
|
||||||
|
public string $senderName;
|
||||||
|
public string $senderBic;
|
||||||
|
|
||||||
|
/* @var Invoice[] */
|
||||||
|
public array $invoices;
|
||||||
|
|
||||||
|
public function __construct(string $senderIban, string $senderName, string $senderBic, array $invoices) {
|
||||||
|
$this->senderIban = $senderIban;
|
||||||
|
$this->senderName = $senderName;
|
||||||
|
$this->senderBic = $senderBic;
|
||||||
|
$this->invoices = $invoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPainFileContent() : string {
|
||||||
|
$doc = new DOMDocument('1.0', 'UTF-8');
|
||||||
|
$doc->formatOutput = true;
|
||||||
|
|
||||||
|
$senderIban = str_replace(' ', '', $this->senderIban);
|
||||||
|
$senderBic = str_replace(' ', '', $this->senderBic);
|
||||||
|
|
||||||
|
// Root-Element
|
||||||
|
$document = $doc->createElement('Document');
|
||||||
|
$document->setAttribute('xmlns', 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03');
|
||||||
|
$document->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
|
||||||
|
$doc->appendChild($document);
|
||||||
|
|
||||||
|
// CstmrCdtTrfInitn
|
||||||
|
$cstmr_cdt_trf_initn = $doc->createElement('CstmrCdtTrfInitn');
|
||||||
|
$document->appendChild($cstmr_cdt_trf_initn);
|
||||||
|
|
||||||
|
// GrpHdr
|
||||||
|
$grp_hdr = $doc->createElement('GrpHdr');
|
||||||
|
$cstmr_cdt_trf_initn->appendChild($grp_hdr);
|
||||||
|
|
||||||
|
$grp_hdr->appendChild($doc->createElement('MsgId', uniqid('MSG')));
|
||||||
|
$grp_hdr->appendChild($doc->createElement('CreDtTm', date('c')));
|
||||||
|
$grp_hdr->appendChild($doc->createElement('NbOfTxs', count($this->invoices)));
|
||||||
|
|
||||||
|
$totalAmount = array_sum(array_column($this->invoices, 'amount'));
|
||||||
|
$grp_hdr->appendChild($doc->createElement('CtrlSum', number_format($totalAmount, 2, '.', '')));
|
||||||
|
|
||||||
|
$initg_pty = $doc->createElement('InitgPty');
|
||||||
|
$initg_pty->appendChild($doc->createElement('Nm', $this->senderName));
|
||||||
|
$grp_hdr->appendChild($initg_pty);
|
||||||
|
|
||||||
|
// PmtInf
|
||||||
|
$pmt_inf = $doc->createElement('PmtInf');
|
||||||
|
$cstmr_cdt_trf_initn->appendChild($pmt_inf);
|
||||||
|
|
||||||
|
$pmt_inf->appendChild($doc->createElement('PmtInfId', uniqid('PMT')));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('PmtMtd', 'TRF'));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('BtchBookg', 'false'));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('NbOfTxs', count($this->invoices)));
|
||||||
|
$pmt_inf->appendChild($doc->createElement('CtrlSum', number_format($totalAmount, 2, '.', '')));
|
||||||
|
|
||||||
|
$pmt_tp_inf = $doc->createElement('PmtTpInf');
|
||||||
|
$svc_lvl = $doc->createElement('SvcLvl');
|
||||||
|
$svc_lvl->appendChild($doc->createElement('Cd', 'SEPA'));
|
||||||
|
$pmt_tp_inf->appendChild($svc_lvl);
|
||||||
|
$pmt_inf->appendChild($pmt_tp_inf);
|
||||||
|
|
||||||
|
$pmt_inf->appendChild($doc->createElement('ReqdExctnDt', date('Y-m-d', mktime(0))));
|
||||||
|
|
||||||
|
$dbtr = $doc->createElement('Dbtr');
|
||||||
|
$dbtr->appendChild($doc->createElement('Nm', $this->senderName));
|
||||||
|
$pmt_inf->appendChild($dbtr);
|
||||||
|
|
||||||
|
$dbtr_acct = $doc->createElement('DbtrAcct');
|
||||||
|
$id = $doc->createElement('Id');
|
||||||
|
$id->appendChild($doc->createElement('IBAN', $senderIban));
|
||||||
|
$dbtr_acct->appendChild($id);
|
||||||
|
$pmt_inf->appendChild($dbtr_acct);
|
||||||
|
|
||||||
|
|
||||||
|
$dbtr_agt = $doc->createElement('DbtrAgt');
|
||||||
|
$id = $doc->createElement('FinInstnId');
|
||||||
|
$id->appendChild($doc->createElement('BIC', $senderBic));
|
||||||
|
$dbtr_agt->appendChild($id);
|
||||||
|
$pmt_inf->appendChild($dbtr_agt);
|
||||||
|
|
||||||
|
foreach ($this->invoices as $index => $invoice) {
|
||||||
|
$cdt_trf_tx_inf = $doc->createElement('CdtTrfTxInf');
|
||||||
|
|
||||||
|
$pmt_id = $doc->createElement('PmtId');
|
||||||
|
$pmt_id->appendChild($doc->createElement('EndToEndId', uniqid('ET')));
|
||||||
|
$cdt_trf_tx_inf->appendChild($pmt_id);
|
||||||
|
|
||||||
|
$amt = $doc->createElement('Amt');
|
||||||
|
$instd_amt = $doc->createElement('InstdAmt', number_format($invoice['amount'], 2, '.', ''));
|
||||||
|
$instd_amt->setAttribute('Ccy', 'EUR');
|
||||||
|
$amt->appendChild($instd_amt);
|
||||||
|
$cdt_trf_tx_inf->appendChild($amt);
|
||||||
|
|
||||||
|
$cdtr = $doc->createElement('Cdtr');
|
||||||
|
$cdtr->appendChild($doc->createElement('Nm', $invoice['contact_bank_owner']));
|
||||||
|
$cdt_trf_tx_inf->appendChild($cdtr);
|
||||||
|
|
||||||
|
$cdtr_acct = $doc->createElement('CdtrAcct');
|
||||||
|
$cdtr_id = $doc->createElement('Id');
|
||||||
|
$cdtr_id->appendChild($doc->createElement('IBAN', str_replace(' ', '', $invoice['contact_bank_iban'])));
|
||||||
|
$cdtr_acct->appendChild($cdtr_id);
|
||||||
|
$cdt_trf_tx_inf->appendChild($cdtr_acct);
|
||||||
|
|
||||||
|
$rmt_inf = $doc->createElement('RmtInf');
|
||||||
|
$rmt_inf->appendChild($doc->createElement('Ustrd', 'Auslagenerstattung Rechnungsnummer ' . $invoice['invoice_number']));
|
||||||
|
$cdt_trf_tx_inf->appendChild($rmt_inf);
|
||||||
|
|
||||||
|
$pmt_inf->appendChild($cdt_trf_tx_inf);
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml_content = $doc->saveXML();
|
||||||
|
if (false === $xml_content) {
|
||||||
|
throw new Exception('Failed to generate XML content.');
|
||||||
|
}
|
||||||
|
return $xml_content;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/Providers/PdfMergeProvider.php
Normal file
46
app/Providers/PdfMergeProvider.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
use setasign\Fpdi\Fpdi;
|
||||||
|
|
||||||
|
class PdfMergeProvider {
|
||||||
|
protected array $files = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt eine PDF-Datei zur Merge-Liste hinzu.
|
||||||
|
*/
|
||||||
|
public function add(string $filePath): self
|
||||||
|
{
|
||||||
|
$this->files[] = $filePath;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Führt alle hinzugefügten PDFs zusammen und speichert sie.
|
||||||
|
*/
|
||||||
|
public function merge(string $outputPath): void
|
||||||
|
{
|
||||||
|
if (empty($this->files)) {
|
||||||
|
throw new RuntimeException("No PDF files added for merging.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf = new FPDI();
|
||||||
|
|
||||||
|
foreach ($this->files as $file) {
|
||||||
|
$pageCount = $pdf->setSourceFile($file);
|
||||||
|
|
||||||
|
for ($page = 1; $page <= $pageCount; $page++) {
|
||||||
|
$tpl = $pdf->importPage($page);
|
||||||
|
$size = $pdf->getTemplateSize($tpl);
|
||||||
|
|
||||||
|
$pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
|
||||||
|
$pdf->useTemplate($tpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->Output('F', $outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Providers/WebDavProvider.php
Normal file
27
app/Providers/WebDavProvider.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
use WebDav\WebDav\WebDavClient;
|
||||||
|
class WebDavProvider {
|
||||||
|
public const INVOICE_PREFIX = 'Abrechnungen ';
|
||||||
|
|
||||||
|
private WebDavClient $webDavClient;
|
||||||
|
private string $workingDirectory = '/';
|
||||||
|
|
||||||
|
public function __construct($workingDirectory) {
|
||||||
|
$this->webDavClient = new WebDavClient(env('WEBDAV_HOST'), env('WEBDAV_USER'), env('WEBDAV_PASS'));
|
||||||
|
$this->workingDirectory = $workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadFile(string $fileName) {
|
||||||
|
$baseDir = storage_path('app/private/');
|
||||||
|
|
||||||
|
$this->webDavClient->upload_file($baseDir . $fileName, $this->workingDirectory . '/'.
|
||||||
|
basename($fileName)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
app/Providers/ZipArchiveFileProvider.php
Normal file
29
app/Providers/ZipArchiveFileProvider.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class ZipArchiveFileProvider {
|
||||||
|
private ZipArchive $zipArchive;
|
||||||
|
|
||||||
|
public function __construct(string $filename) {
|
||||||
|
$this->zipArchive = new ZipArchive();
|
||||||
|
$this->zipArchive->open(storage_path('app/private/' . $filename), ZipArchive::CREATE | ZipArchive::OVERWRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFile(string $filename) {
|
||||||
|
$filename = storage_path('app/private/' . $filename);
|
||||||
|
if (!file_exists($filename)) {
|
||||||
|
dd($filename);
|
||||||
|
}
|
||||||
|
$this->zipArchive->addFile($filename, basename($filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create() : bool {
|
||||||
|
$this->zipArchive->close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,10 +39,15 @@ class InvoiceRepository {
|
|||||||
return $invoices;
|
return $invoices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByStatus(CostUnit $costUnit, string $status) : array {
|
public function getByStatus(CostUnit $costUnit, string $status, bool $forDisplay = true) : array {
|
||||||
$returnData = [];
|
$returnData = [];
|
||||||
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
||||||
|
if ($forDisplay) {
|
||||||
$returnData[] = new InvoiceResource($invoice)->toArray();
|
$returnData[] = new InvoiceResource($invoice)->toArray();
|
||||||
|
} else {
|
||||||
|
$returnData[] = $invoice;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return $returnData;
|
return $returnData;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class InvoiceResource {
|
|||||||
$returnData['invoiceType'] .= ' (' . $this->invoice->type_other . ')';
|
$returnData['invoiceType'] .= ' (' . $this->invoice->type_other . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$returnData['invoiceTypeShort'] = $this->invoice->invoiceType()->name;
|
||||||
$returnData['costUnitName'] = $this->invoice->costUnit()->first()->name;
|
$returnData['costUnitName'] = $this->invoice->costUnit()->first()->name;
|
||||||
$returnData['invoiceNumber'] = $this->invoice->invoice_number;
|
$returnData['invoiceNumber'] = $this->invoice->invoice_number;
|
||||||
$returnData['contactName'] = $this->invoice->contact_name;
|
$returnData['contactName'] = $this->invoice->contact_name;
|
||||||
@@ -47,7 +48,17 @@ class InvoiceResource {
|
|||||||
$returnData['costUnitId'] = $this->invoice->cost_unit_id;
|
$returnData['costUnitId'] = $this->invoice->cost_unit_id;
|
||||||
$returnData['amountPlain'] = new Amount($this->invoice->amount, '')->toString();
|
$returnData['amountPlain'] = new Amount($this->invoice->amount, '')->toString();
|
||||||
$returnData['internalType'] = $this->invoice->type;
|
$returnData['internalType'] = $this->invoice->type;
|
||||||
|
$returnData['donationText'] = $this->invoice->donation ? 'Ja' : 'Nein';
|
||||||
|
$returnData['createdAt'] = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->created_at)->format('d.m.Y H:i');
|
||||||
|
$returnData['approvedAt'] = '--';
|
||||||
|
$returnData['approvedBy'] = '--';
|
||||||
|
|
||||||
|
if ($this->invoice->status === InvoiceStatus::INVOICE_STATUS_APPROVED) {
|
||||||
|
$returnData['approvedAt'] = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->approved_at)->format('d.m.Y H:i');
|
||||||
|
$returnData['approvedBy'] = $this->invoice->approvedBy()->first()->username ?? '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnData['alreadyPaid'] = $returnData['alreadyPaid'] == '' ? 0 : 1;
|
||||||
|
|
||||||
return $returnData;
|
return $returnData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Scopes;
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
use App\Providers\AuthCheckProvider;
|
use App\Providers\AuthCheckProvider;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
@@ -9,6 +10,7 @@ use App\Repositories\PageTextRepository;
|
|||||||
use App\Repositories\UserRepository;
|
use App\Repositories\UserRepository;
|
||||||
|
|
||||||
abstract class CommonController {
|
abstract class CommonController {
|
||||||
|
protected Tenant $tenant;
|
||||||
protected UserRepository $users;
|
protected UserRepository $users;
|
||||||
protected CostUnitRepository $costUnits;
|
protected CostUnitRepository $costUnits;
|
||||||
|
|
||||||
@@ -17,6 +19,7 @@ abstract class CommonController {
|
|||||||
protected InvoiceRepository $invoices;
|
protected InvoiceRepository $invoices;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
$this->tenant = app('tenant');
|
||||||
$this->users = new UserRepository();
|
$this->users = new UserRepository();
|
||||||
$this->costUnits = new CostUnitRepository();
|
$this->costUnits = new CostUnitRepository();
|
||||||
$this->pageTexts = new PageTextRepository();
|
$this->pageTexts = new PageTextRepository();
|
||||||
|
|||||||
57
app/Views/Components/LoadingModal.vue
Normal file
57
app/Views/Components/LoadingModal.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<Modal :show="show" title="Bitte warten">
|
||||||
|
<div class="loading-wrapper">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Modal from './Modal.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
show: Boolean,
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: "Bitte warte während die Anfrage bearbeitet wird ..."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.loading-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px;
|
||||||
|
gap: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* klassischer Spinner */
|
||||||
|
.spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 4px solid #ddd;
|
||||||
|
border-top-color: #47a0d8;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,7 +9,10 @@
|
|||||||
"php": "^8.5",
|
"php": "^8.5",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^2.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1"
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"contelli/webdav-sync": "^1.0",
|
||||||
|
"ext-zip": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
@@ -18,7 +21,11 @@
|
|||||||
"laravel/sail": "^1.41",
|
"laravel/sail": "^1.41",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.6",
|
"nunomaduro/collision": "^8.6",
|
||||||
"phpunit/phpunit": "^11.5.3"
|
"phpunit/phpunit": "^11.5.3",
|
||||||
|
"dompdf/dompdf": "^3.0",
|
||||||
|
"setasign/fpdf": "^1.8",
|
||||||
|
"setasign/fpdi": "^2.6",
|
||||||
|
"maennchen/zipstream-php": "^3.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
932
composer.lock
generated
932
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,13 @@ return new class extends Migration {
|
|||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('email');
|
$table->string('email');
|
||||||
$table->string('url');
|
$table->string('url');
|
||||||
|
$table->string('account_name');
|
||||||
$table->string('account_iban');
|
$table->string('account_iban');
|
||||||
|
$table->string('account_bic');
|
||||||
$table->string('city');
|
$table->string('city');
|
||||||
$table->string('postcode');
|
$table->string('postcode');
|
||||||
|
$table->boolean('download_exports')->default(false);
|
||||||
|
$table->boolean('upload_exports')->default(true);
|
||||||
$table->string('gdpr_text')-> nullable();
|
$table->string('gdpr_text')-> nullable();
|
||||||
$table->string('impress_text')->nullable();
|
$table->string('impress_text')->nullable();
|
||||||
$table->string('url_participation_rules')->nullable();
|
$table->string('url_participation_rules')->nullable();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ return new class extends Migration {
|
|||||||
$table->float('amount', 2);
|
$table->float('amount', 2);
|
||||||
$table->integer('distance')->nullable();
|
$table->integer('distance')->nullable();
|
||||||
$table->string('comment')->nullable();
|
$table->string('comment')->nullable();
|
||||||
$table->string('changes')->nullable();
|
$table->longText('changes')->nullable();
|
||||||
$table->string('travel_direction')->nullable();
|
$table->string('travel_direction')->nullable();
|
||||||
$table->boolean('passengers')->nullable();
|
$table->boolean('passengers')->nullable();
|
||||||
$table->boolean('transportation')->nullable();
|
$table->boolean('transportation')->nullable();
|
||||||
|
|||||||
Reference in New Issue
Block a user