initial commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
require_once "../config/config.php";
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) throw new RuntimeException('id fehlt');
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM finance_expenses WHERE id=:id");
|
||||
$stmt->execute([':id'=>$id]);
|
||||
|
||||
echo json_encode(['ok'=>true], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
require_once "../config/config.php";
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$from = $_GET['from'] ?? date('Y-m-01');
|
||||
$to = $_GET['to'] ?? date('Y-m-t');
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, date, category, title, amount_eur, method, notes
|
||||
FROM finance_expenses
|
||||
WHERE date BETWEEN :from AND :to
|
||||
ORDER BY date DESC, id DESC
|
||||
");
|
||||
$stmt->execute([':from' => $from, ':to' => $to]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$sum = 0;
|
||||
foreach ($rows as $r) $sum += (int)$r['amount_eur'];
|
||||
|
||||
echo json_encode(['ok'=>true,'from'=>$from,'to'=>$to,'sum_eur'=>$sum,'items'=>$rows], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once "../config/config.php";
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||
|
||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||
$date = trim($_POST['date'] ?? '');
|
||||
$category = trim($_POST['category'] ?? '');
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$amount = (int)($_POST['amount_eur'] ?? 0);
|
||||
$method = trim($_POST['method'] ?? 'bank');
|
||||
$notes = trim($_POST['notes'] ?? '');
|
||||
|
||||
if (!$date || !$category || !$title) throw new RuntimeException('Pflichtfelder fehlen');
|
||||
if ($amount <= 0) throw new RuntimeException('Betrag muss > 0 sein');
|
||||
|
||||
$allowedMethods = ['cash','bank','paypal','card','other'];
|
||||
if (!in_array($method, $allowedMethods, true)) $method = 'bank';
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE finance_expenses
|
||||
SET date=:date, category=:category, title=:title, amount_eur=:amount, method=:method, notes=:notes
|
||||
WHERE id=:id
|
||||
");
|
||||
$stmt->execute([
|
||||
':date'=>$date, ':category'=>$category, ':title'=>$title, ':amount'=>$amount, ':method'=>$method, ':notes'=>$notes, ':id'=>$id
|
||||
]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO finance_expenses (date, category, title, amount_eur, method, notes)
|
||||
VALUES (:date,:category,:title,:amount,:method,:notes)
|
||||
");
|
||||
$stmt->execute([
|
||||
':date'=>$date, ':category'=>$category, ':title'=>$title, ':amount'=>$amount, ':method'=>$method, ':notes'=>$notes
|
||||
]);
|
||||
$id = (int)$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
echo json_encode(['ok'=>true,'id'=>$id], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
require_once "../config/config.php";
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
// TODO: Statuswert anpassen:
|
||||
// Beispiel: 2 = erledigt/abgeschlossen
|
||||
$STATUS_DONE = 2;
|
||||
|
||||
$from = $_GET['from'] ?? null; // YYYY-MM-DD
|
||||
$to = $_GET['to'] ?? null;
|
||||
|
||||
// Default: aktueller Monat
|
||||
if (!$from || !$to) {
|
||||
$from = date('Y-m-01');
|
||||
$to = date('Y-m-t');
|
||||
}
|
||||
|
||||
$fromTs = strtotime($from . ' 00:00:00');
|
||||
$toTs = strtotime($to . ' 23:59:59');
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
id,
|
||||
startzeit,
|
||||
endzeit,
|
||||
locationtype,
|
||||
location,
|
||||
gage,
|
||||
status,
|
||||
beschreibung
|
||||
FROM termine
|
||||
WHERE startzeit BETWEEN :fromTs AND :toTs
|
||||
AND status = :done
|
||||
AND gage > 0
|
||||
ORDER BY startzeit DESC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':fromTs' => $fromTs,
|
||||
':toTs' => $toTs,
|
||||
':done' => $STATUS_DONE,
|
||||
]);
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$sum = 0;
|
||||
foreach ($rows as $r) $sum += (int)$r['gage'];
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'sum_eur' => $sum,
|
||||
'items' => array_map(function($r){
|
||||
return [
|
||||
'id' => (int)$r['id'],
|
||||
'date' => date('Y-m-d', (int)$r['startzeit']),
|
||||
'time' => date('H:i', (int)$r['startzeit']) . '–' . date('H:i', (int)$r['endzeit']),
|
||||
'location' => trim(($r['locationtype'] ?? '') . ' ' . ($r['location'] ?? '')),
|
||||
'gage' => (int)$r['gage'],
|
||||
'desc' => (string)($r['beschreibung'] ?? ''),
|
||||
];
|
||||
}, $rows),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once "../config/config.php";
|
||||
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||
|
||||
global $pdo;
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
throw new RuntimeException('Method not allowed');
|
||||
}
|
||||
|
||||
$terminId = (int)($_POST['termin_id'] ?? 0);
|
||||
if ($terminId <= 0) {
|
||||
throw new RuntimeException('termin_id fehlt');
|
||||
}
|
||||
|
||||
// Termin laden
|
||||
$st = $pdo->prepare("SELECT * FROM termine WHERE id=:id");
|
||||
$st->execute([':id' => $terminId]);
|
||||
$t = $st->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$t) {
|
||||
throw new RuntimeException('Termin nicht gefunden');
|
||||
}
|
||||
|
||||
// Prüfen ob Rechnung existiert
|
||||
$st = $pdo->prepare("SELECT id FROM invoices WHERE termine_id=:tid LIMIT 1");
|
||||
$st->execute([':tid' => $terminId]);
|
||||
|
||||
if ($st->fetch()) {
|
||||
throw new RuntimeException('Für diesen Termin existiert bereits eine Rechnung.');
|
||||
}
|
||||
|
||||
$serviceDate = date('Y-m-d', (int)$t['startzeit']);
|
||||
$invoiceDate = date('Y-m-d');
|
||||
$dueDate = date('Y-m-d', strtotime($invoiceDate . ' +14 days'));
|
||||
|
||||
$year = (int)date('Y');
|
||||
$invoiceNo = nextInvoiceNo($pdo, $year);
|
||||
|
||||
$customerName = trim($_POST['customer_name'] ?? 'Kunde (bitte bearbeiten)');
|
||||
$customerStreet = trim($_POST['customer_street'] ?? '');
|
||||
$customerZip = trim($_POST['customer_zip'] ?? '');
|
||||
$customerCity = trim($_POST['customer_city'] ?? '');
|
||||
$customerCountry = trim($_POST['customer_country'] ?? 'DE');
|
||||
|
||||
$gage = (int)($t['gage'] ?? 0);
|
||||
|
||||
$desc = "DJ-Leistung am " . date('d.m.Y', (int)$t['startzeit']);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$pdo->prepare("
|
||||
INSERT INTO invoices
|
||||
(invoice_no, invoice_date, service_date, due_date, status,
|
||||
customer_name, customer_street, customer_zip, customer_city, customer_country,
|
||||
termine_id, amount_net_eur)
|
||||
VALUES
|
||||
(:no, :invdate, :servdate, :duedate, 'draft',
|
||||
:cname, :cstreet, :czip, :ccity, :ccountry,
|
||||
:tid, :amount)
|
||||
")->execute([
|
||||
':no' => $invoiceNo,
|
||||
':invdate' => $invoiceDate,
|
||||
':servdate' => $serviceDate,
|
||||
':duedate' => $dueDate,
|
||||
':cname' => $customerName,
|
||||
':cstreet' => $customerStreet,
|
||||
':czip' => $customerZip,
|
||||
':ccity' => $customerCity,
|
||||
':ccountry' => $customerCountry,
|
||||
':tid' => $terminId,
|
||||
':amount' => $gage
|
||||
]);
|
||||
|
||||
$invoiceId = (int)$pdo->lastInsertId();
|
||||
|
||||
$pdo->prepare("
|
||||
INSERT INTO invoice_items
|
||||
(invoice_id, pos, description, qty, unit_price_eur, line_total_eur)
|
||||
VALUES
|
||||
(:iid, 1, :d, 1, :p, :p)
|
||||
")->execute([
|
||||
':iid' => $invoiceId,
|
||||
':d' => $desc,
|
||||
':p' => $gage
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'invoice_id' => $invoiceId,
|
||||
'invoice_no' => $invoiceNo
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
|
||||
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once "../config/config.php";
|
||||
global $pdo;
|
||||
|
||||
try {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if ($id <= 0) throw new RuntimeException('id fehlt');
|
||||
|
||||
$st = $pdo->prepare("SELECT * FROM invoices WHERE id=:id");
|
||||
$st->execute([':id'=>$id]);
|
||||
$inv = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$inv) throw new RuntimeException('Rechnung nicht gefunden');
|
||||
|
||||
$it = $pdo->prepare("SELECT * FROM invoice_items WHERE invoice_id=:id ORDER BY pos ASC, id ASC");
|
||||
$it->execute([':id'=>$id]);
|
||||
$items = $it->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['ok'=>true,'invoice'=>$inv,'items'=>$items], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once "../config/config.php";
|
||||
global $pdo;
|
||||
|
||||
try {
|
||||
$from = $_GET['from'] ?? date('Y-m-01');
|
||||
$to = $_GET['to'] ?? date('Y-m-t');
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, invoice_no, invoice_date, service_date, due_date, status,
|
||||
customer_name, amount_net_eur, termine_id, pdf_path
|
||||
FROM invoices
|
||||
WHERE invoice_date BETWEEN :from AND :to
|
||||
ORDER BY invoice_date DESC, id DESC
|
||||
");
|
||||
$stmt->execute([':from'=>$from, ':to'=>$to]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['ok'=>true,'from'=>$from,'to'=>$to,'items'=>$rows], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
set_exception_handler(function(Throwable $e){
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
});
|
||||
set_error_handler(function($severity, $message, $file, $line){
|
||||
throw new ErrorException($message, 0, $severity, $file, $line);
|
||||
});
|
||||
|
||||
|
||||
require_once "../config/config.php";
|
||||
global $pdo;
|
||||
|
||||
$config = require __DIR__ . '/../config/invoice.php';
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
|
||||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
|
||||
function eur($n){ return number_format((int)$n, 0, ',', '.') . " €"; }
|
||||
|
||||
function imgToDataUri(string $path): string {
|
||||
if (!file_exists($path)) {
|
||||
throw new RuntimeException("Logo nicht gefunden: $path");
|
||||
}
|
||||
if (!is_readable($path)) {
|
||||
throw new RuntimeException("Logo nicht lesbar (Rechte): $path");
|
||||
}
|
||||
|
||||
$data = file_get_contents($path);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException("Logo konnte nicht gelesen werden: $path");
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
$mime = match($ext) {
|
||||
'png' => 'image/png',
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'svg' => 'image/svg+xml',
|
||||
default => throw new RuntimeException("Logo-Format nicht unterstützt: .$ext (bitte png/jpg/svg)")
|
||||
};
|
||||
|
||||
return "data:$mime;base64," . base64_encode($data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
try {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if ($id <= 0) throw new RuntimeException('id fehlt');
|
||||
|
||||
// Load invoice + items
|
||||
$st = $pdo->prepare("SELECT * FROM invoices WHERE id=:id");
|
||||
$st->execute([':id'=>$id]);
|
||||
$inv = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$inv) throw new RuntimeException('Rechnung nicht gefunden');
|
||||
|
||||
$it = $pdo->prepare("SELECT * FROM invoice_items WHERE invoice_id=:id ORDER BY pos ASC, id ASC");
|
||||
$it->execute([':id'=>$id]);
|
||||
$items = $it->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Ensure pdf dir
|
||||
$pdfDir = $config['pdf_dir'];
|
||||
if (!is_dir($pdfDir)) {
|
||||
if (!mkdir($pdfDir, 0775, true)) throw new RuntimeException('pdf_dir kann nicht erstellt werden');
|
||||
}
|
||||
|
||||
$issuer = $config['issuer'];
|
||||
$pay = $config['payment'];
|
||||
|
||||
$branding = $config['branding'] ?? [];
|
||||
$accent = $branding['accent'] ?? '#111';
|
||||
$logoData = imgToDataUri($branding['logo_path'] ?? null);
|
||||
|
||||
|
||||
$logoHtml = '';
|
||||
if ($logoData) {
|
||||
$logoHtml = '<img src="'.h($logoData).'" style="height:52px;" alt="ANJUMA Logo">';
|
||||
}
|
||||
|
||||
$html = '
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
@page { margin: 26px 30px; }
|
||||
|
||||
body {
|
||||
font-family: DejaVu Sans, Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
/* ===== HEADER ===== */
|
||||
.header {
|
||||
width:100%;
|
||||
}
|
||||
.header-left {
|
||||
float:left;
|
||||
width:60%;
|
||||
}
|
||||
.header-right {
|
||||
float:right;
|
||||
width:38%;
|
||||
text-align:right;
|
||||
}
|
||||
.clear { clear:both; }
|
||||
|
||||
.header-name {
|
||||
font-size:14px;
|
||||
font-weight:700;
|
||||
}
|
||||
.header-address {
|
||||
font-size:10px;
|
||||
color:#666;
|
||||
}
|
||||
|
||||
.line {
|
||||
height:4px;
|
||||
background: '.$accent.';
|
||||
margin: 12px 0 18px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size:28px;
|
||||
font-weight:800;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
/* ===== META ===== */
|
||||
.meta {
|
||||
width:100%;
|
||||
margin-bottom:14px;
|
||||
}
|
||||
.meta td {
|
||||
padding:2px 0;
|
||||
}
|
||||
.meta .k {
|
||||
width:140px;
|
||||
color:#666;
|
||||
}
|
||||
.meta .v {
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
/* ===== SECTIONS ===== */
|
||||
.section {
|
||||
margin-top:18px;
|
||||
}
|
||||
.section-title {
|
||||
font-size:10px;
|
||||
letter-spacing:.06em;
|
||||
text-transform:uppercase;
|
||||
color:#666;
|
||||
margin-bottom:6px;
|
||||
}
|
||||
.section-content {
|
||||
border-top:2px solid #111;
|
||||
padding-top:8px;
|
||||
}
|
||||
|
||||
/* ===== ADDRESS ===== */
|
||||
.addr-left {
|
||||
float:left;
|
||||
width:48%;
|
||||
}
|
||||
.addr-right {
|
||||
float:right;
|
||||
width:48%;
|
||||
}
|
||||
|
||||
/* ===== ITEMS TABLE ===== */
|
||||
table.items {
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
margin-top:20px;
|
||||
}
|
||||
table.items th {
|
||||
background:#f3f3f3;
|
||||
padding:8px;
|
||||
font-size:10px;
|
||||
text-transform:uppercase;
|
||||
border-top:1px solid #ccc;
|
||||
border-bottom:1px solid #ccc;
|
||||
}
|
||||
table.items td {
|
||||
padding:10px 8px;
|
||||
border-bottom:1px solid #eee;
|
||||
}
|
||||
.num {
|
||||
text-align:right;
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
/* ===== SUMMARY ===== */
|
||||
.summary {
|
||||
margin-top:16px;
|
||||
}
|
||||
.summary-note {
|
||||
float:left;
|
||||
width:58%;
|
||||
font-size:10px;
|
||||
color:#555;
|
||||
line-height:1.4;
|
||||
}
|
||||
.summary-bar {
|
||||
float:right;
|
||||
width:40%;
|
||||
background:#f3f3f3;
|
||||
border-top:3px solid #111;
|
||||
padding:10px 12px;
|
||||
}
|
||||
.summary-bar table {
|
||||
width:100%;
|
||||
}
|
||||
.summary-bar td {
|
||||
padding:4px 0;
|
||||
}
|
||||
.summary-bar .grand {
|
||||
font-size:18px;
|
||||
font-weight:800;
|
||||
}
|
||||
|
||||
/* ===== PAYMENT ===== */
|
||||
.payment {
|
||||
margin-top:20px;
|
||||
border-top:2px solid #111;
|
||||
padding-top:8px;
|
||||
}
|
||||
.payment td {
|
||||
padding:3px 0;
|
||||
}
|
||||
.muted { color:#666; }
|
||||
|
||||
/* ===== FOOTER ===== */
|
||||
.footer {
|
||||
position: fixed;
|
||||
left:30px;
|
||||
right:30px;
|
||||
bottom:18px;
|
||||
font-size:9px;
|
||||
color:#666;
|
||||
border-top:1px solid #eee;
|
||||
padding-top:6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="header-name">'.h($issuer['name']).'</div>
|
||||
<div class="header-address">
|
||||
'.h($issuer['street']).' • '.h($issuer['zip']).' '.h($issuer['city']).'
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
'.$logoHtml.'
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="title">Rechnung</div>
|
||||
|
||||
<table class="meta">
|
||||
<tr><td class="k">Rechnungsnummer</td><td class="v">'.h($inv['invoice_no']).'</td></tr>
|
||||
<tr><td class="k">Rechnungsdatum</td><td class="v">'.h(date('d.m.Y', strtotime($inv['invoice_date']))).'</td></tr>
|
||||
<tr><td class="k">Leistungsdatum</td><td class="v">'.h(date('d.m.Y', strtotime($inv['service_date']))).'</td></tr>
|
||||
<tr><td class="k">Fällig am</td><td class="v">'.h(date('d.m.Y', strtotime($inv['due_date']))).'</td></tr>
|
||||
</table>
|
||||
|
||||
<!-- ADDRESSES -->
|
||||
<div class="section">
|
||||
<div class="addr-left">
|
||||
<div class="section-title">Rechnung an</div>
|
||||
<div class="section-content">
|
||||
<b>'.h($inv['customer_name']).'</b><br>
|
||||
'.($inv['customer_street'] ? h($inv['customer_street']).'<br>' : '').'
|
||||
'.h($inv['customer_zip']).' '.h($inv['customer_city']).'<br>
|
||||
'.h($inv['customer_country']).'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="addr-right">
|
||||
<div class="section-title">Aussteller</div>
|
||||
<div class="section-content">
|
||||
<b>'.h($issuer['name']).'</b><br>
|
||||
'.h($issuer['street']).'<br>
|
||||
'.h($issuer['zip']).' '.h($issuer['city']).'<br>
|
||||
'.h($issuer['country']).'<br><br>
|
||||
<span class="muted">Steuernr.:</span> '.h($issuer['tax_no']).'<br>
|
||||
<span class="muted">E-Mail:</span> '.h($issuer['email']).'<br>
|
||||
<span class="muted">Tel.:</span> '.h($issuer['phone']).'
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<!-- ITEMS -->
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40px;">Pos</th>
|
||||
<th>Beschreibung</th>
|
||||
<th class="num" style="width:70px;">Menge</th>
|
||||
<th class="num" style="width:110px;">Einzelpreis</th>
|
||||
<th class="num" style="width:110px;">Gesamt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
|
||||
|
||||
// --- ITEMS LOOP (wie gehabt) ---
|
||||
$pos = 1;
|
||||
$sum = 0;
|
||||
|
||||
foreach ($items as $row) {
|
||||
$line = (int)$row['line_total_eur'];
|
||||
$sum += $line;
|
||||
|
||||
$html .= '
|
||||
<tr>
|
||||
<td>'.h($row['pos'] ?? $pos).'</td>
|
||||
<td>'.h($row['description']).'</td>
|
||||
<td class="num">'.h($row['qty']).'</td>
|
||||
<td class="num">'.h(eur($row['unit_price_eur'])).'</td>
|
||||
<td class="num"><b>'.h(eur($line)).'</b></td>
|
||||
</tr>';
|
||||
$pos++;
|
||||
}
|
||||
|
||||
// --- CLOSE TABLE + SUMMARY + PAYMENT + FOOTER ---
|
||||
$html .= '
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- SUMMARY -->
|
||||
<div class="summary">
|
||||
<div class="summary-note">
|
||||
'.h($config['kleinunternehmer_text']).'
|
||||
'.(!empty($inv['notes']) ? '<br><br>'.nl2br(h($inv['notes'])) : '').'
|
||||
</div>
|
||||
|
||||
<div class="summary-bar">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="muted">Zwischensumme</td>
|
||||
<td class="num">'.h(eur($sum)).'</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="muted">Umsatzsteuer</td>
|
||||
<td class="num">0 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-top:8px;"><b>Gesamtbetrag</b></td>
|
||||
<td class="num grand">'.h(eur($sum)).'</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<!-- PAYMENT -->
|
||||
<div class="payment">
|
||||
<div class="section-title">Zahlungsinformationen</div>
|
||||
<div class="section-content" style="border-top:0; padding-top:0;">
|
||||
<div style="margin-bottom:8px;">
|
||||
Bitte überweisen Sie den Betrag innerhalb von '.h($pay['pay_within_days']).' Tagen.
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td style="width:70px;" class="muted">IBAN</td>
|
||||
<td><b>'.h($pay['iban']).'</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="muted">BIC</td>
|
||||
<td><b>'.h($pay['bic']).'</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="muted">Bank</td>
|
||||
<td><b>'.h($pay['bank']).'</b></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
'.h($issuer['name']).' • '.h($issuer['street']).', '.h($issuer['zip']).' '.h($issuer['city']).' • Steuernr.: '.h($issuer['tax_no']).'
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>';
|
||||
|
||||
|
||||
|
||||
|
||||
// Dompdf options
|
||||
$options = new Options();
|
||||
$options->set('isRemoteEnabled', false);
|
||||
$options->set('defaultFont', 'DejaVu Sans');
|
||||
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html, 'UTF-8');
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
|
||||
$pdfOutput = $dompdf->output();
|
||||
|
||||
// Save file
|
||||
$filename = $inv['invoice_no'] . '.pdf';
|
||||
$fullPath = rtrim($pdfDir, '/').'/'.$filename;
|
||||
$relativePath = 'storage/invoices/' . $filename;
|
||||
|
||||
if (file_put_contents($fullPath, $pdfOutput) === false) {
|
||||
throw new RuntimeException('PDF konnte nicht gespeichert werden: ' . $fullPath);
|
||||
}
|
||||
|
||||
// Store pdf_path in DB (relative is nicer)
|
||||
$pdo->prepare("UPDATE invoices SET pdf_path=:p WHERE id=:id")
|
||||
->execute([':p'=>$relativePath, ':id'=>$id]);
|
||||
|
||||
// Output download
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: inline; filename="'.basename($filename).'"');
|
||||
echo $pdfOutput;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../lib/PveApi.php';
|
||||
$cfg = require __DIR__ . '/../config/pve.php';
|
||||
$pve = new PveApi($cfg);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$vmid = (int)($_POST['vmid'] ?? 0);
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
|
||||
// optional: hier deine Dashboard-Auth checken!
|
||||
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
|
||||
|
||||
$upid = $pve->powerAction($vmid, $action);
|
||||
echo json_encode(['ok' => true, 'vmid' => $vmid, 'action' => $action, 'upid' => $upid]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../lib/PveApi.php';
|
||||
$cfg = require __DIR__ . '/../config/pve.php';
|
||||
$pve = new PveApi($cfg);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$lxcs = $pve->listLxc(); // array mit vmid, name, status, cpu, mem, etc.
|
||||
$vms = $pve->listQemu(); // array mit vmid, name, status, cpu, mem, etc.
|
||||
|
||||
$all = [];
|
||||
|
||||
foreach ($lxcs as $c) {
|
||||
$vmid = (int)($c['vmid'] ?? 0);
|
||||
if ($vmid >= 100 && $vmid <= 199) {
|
||||
$all[] = [
|
||||
'vmid' => $vmid,
|
||||
'type' => 'lxc',
|
||||
'name' => (string)($c['name'] ?? "LXC {$vmid}"),
|
||||
'status' => (string)($c['status'] ?? 'unknown'),
|
||||
'uptime' => (int)($c['uptime'] ?? 0),
|
||||
'cpu' => (float)($c['cpu'] ?? 0),
|
||||
'mem' => (int)($c['mem'] ?? 0),
|
||||
'maxmem' => (int)($c['maxmem'] ?? 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($vms as $v) {
|
||||
$vmid = (int)($v['vmid'] ?? 0);
|
||||
if ($vmid >= 200 && $vmid <= 299) {
|
||||
$all[] = [
|
||||
'vmid' => $vmid,
|
||||
'type' => 'qemu',
|
||||
'name' => (string)($v['name'] ?? "VM {$vmid}"),
|
||||
'status' => (string)($v['status'] ?? 'unknown'),
|
||||
'uptime' => (int)($v['uptime'] ?? 0),
|
||||
'cpu' => (float)($v['cpu'] ?? 0),
|
||||
'mem' => (int)($v['mem'] ?? 0),
|
||||
'maxmem' => (int)($v['maxmem'] ?? 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: erst LXCs, dann VMs oder nach vmid
|
||||
usort($all, fn($a, $b) => $a['vmid'] <=> $b['vmid']);
|
||||
|
||||
echo json_encode(['ok' => true, 'items' => $all], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../lib/PveApi.php';
|
||||
$cfg = require __DIR__ . '/../config/pve.php';
|
||||
$pve = new PveApi($cfg);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$vmid = (int)($_GET['vmid'] ?? 0);
|
||||
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
|
||||
|
||||
$data = $pve->statusCurrent($vmid);
|
||||
echo json_encode(['ok' => true, 'vmid' => $vmid, 'data' => $data]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once "../config/config.php";
|
||||
global $pdo;
|
||||
|
||||
if (!($pdo instanceof PDO)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['ok'=>false,'error'=>'PDO nicht initialisiert']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Filter
|
||||
$from = $_GET['from'] ?? null; // YYYY-MM-DD
|
||||
$to = $_GET['to'] ?? null; // YYYY-MM-DD
|
||||
$status = $_GET['status'] ?? ''; // optional
|
||||
|
||||
// Default: aktueller Monat
|
||||
if (!$from || !$to) {
|
||||
$from = date('Y-m-01');
|
||||
$to = date('Y-m-t');
|
||||
}
|
||||
|
||||
$fromTs = strtotime($from . ' 00:00:00');
|
||||
$toTs = strtotime($to . ' 23:59:59');
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
id,
|
||||
startzeit,
|
||||
endzeit,
|
||||
locationtype,
|
||||
location,
|
||||
gage,
|
||||
status,
|
||||
beschreibung,
|
||||
user_id,
|
||||
anwesend
|
||||
FROM termine
|
||||
WHERE startzeit BETWEEN :fromTs AND :toTs
|
||||
";
|
||||
|
||||
$params = [
|
||||
':fromTs' => $fromTs,
|
||||
':toTs' => $toTs,
|
||||
];
|
||||
|
||||
if ($status !== '' && is_numeric($status)) {
|
||||
$sql .= " AND status = :status ";
|
||||
$params[':status'] = (int)$status;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY startzeit DESC ";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'count' => count($rows),
|
||||
'items' => array_map(function($r){
|
||||
return [
|
||||
'id' => (int)$r['id'],
|
||||
'startzeit' => (int)$r['startzeit'],
|
||||
'endzeit' => (int)$r['endzeit'],
|
||||
'locationtype' => (string)$r['locationtype'],
|
||||
'location' => (string)$r['location'],
|
||||
'gage' => (int)$r['gage'],
|
||||
'status' => (int)$r['status'],
|
||||
'beschreibung' => (string)($r['beschreibung'] ?? ''),
|
||||
'user_id' => (int)$r['user_id'],
|
||||
'anwesend' => $r['anwesend'], // JSON
|
||||
];
|
||||
}, $rows),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) { http_response_code(401); echo json_encode(['ok'=>false]); exit; }
|
||||
if (($_SESSION['role'] ?? 'user') !== 'admin') { http_response_code(403); echo json_encode(['ok'=>false]); exit; }
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Missing id']); exit; }
|
||||
if ($id === (int)($_SESSION['id'] ?? 0)) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Du kannst dich nicht selbst löschen']); exit; }
|
||||
|
||||
$st = $pdo->prepare("DELETE FROM users WHERE id=:id");
|
||||
$st->execute([':id'=>$id]);
|
||||
|
||||
echo json_encode(['ok'=>true]);
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) { http_response_code(401); echo json_encode(['ok'=>false]); exit; }
|
||||
if (($_SESSION['role'] ?? 'user') !== 'admin') { http_response_code(403); echo json_encode(['ok'=>false]); exit; }
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Missing id']); exit; }
|
||||
if ($id === (int)($_SESSION['id'] ?? 0)) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Du kannst dein eigenes Passwort hier nicht resetten']); exit; }
|
||||
|
||||
$st = $pdo->prepare("UPDATE users SET password_hash=NULL, first_login=1 WHERE id=:id");
|
||||
$st->execute([':id'=>$id]);
|
||||
|
||||
echo json_encode(['ok'=>true]);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../Data.class.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok'=>false,'error'=>'Not logged in']); exit;
|
||||
}
|
||||
if (($_SESSION['role'] ?? 'user') !== 'admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['ok'=>false,'error'=>'Admin only']); exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SELECT id, name, username, role, first_login, last_login FROM users ORDER BY role DESC, name ASC");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['ok'=>true,'items'=>$rows], JSON_UNESCAPED_UNICODE);
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../config/session.php";
|
||||
require_once __DIR__ . "/../config/config.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Not logged in']); exit;
|
||||
}
|
||||
if (($_SESSION["role"] ?? "user") !== "admin") {
|
||||
echo json_encode(['ok' => false, 'error' => 'Forbidden']); exit;
|
||||
}
|
||||
|
||||
// CSRF check
|
||||
$csrf = $_POST['csrf'] ?? '';
|
||||
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $csrf)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'CSRF']); exit;
|
||||
}
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) { echo json_encode(['ok'=>false,'error'=>'Invalid id']); exit; }
|
||||
|
||||
// Optional: sich selbst nicht
|
||||
if ($id === (int)($_SESSION['id'] ?? 0)) {
|
||||
echo json_encode(['ok'=>false,'error'=>'Not allowed for yourself']); exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM remember_tokens WHERE user_id = :uid");
|
||||
$stmt->execute([':uid' => $id]);
|
||||
echo json_encode(['ok' => true]);
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['ok' => false, 'error' => 'DB error']);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok'=>false,'error'=>'Not logged in']); exit;
|
||||
}
|
||||
if (($_SESSION['role'] ?? 'user') !== 'admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['ok'=>false,'error'=>'Admin only']); exit;
|
||||
}
|
||||
|
||||
$id = isset($_POST['id']) && $_POST['id'] !== '' ? (int)$_POST['id'] : null;
|
||||
$name = trim((string)($_POST['name'] ?? ''));
|
||||
$username = trim((string)($_POST['username'] ?? ''));
|
||||
$role = (($_POST['role'] ?? 'user') === 'admin') ? 'admin' : 'user';
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
|
||||
if ($name === '' || $username === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok'=>false,'error'=>'Name und Username sind Pflicht']); exit;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($id === null) {
|
||||
// CREATE: Passwort optional -> wenn leer: first_login=1 und password_hash=NULL
|
||||
if ($password === '') {
|
||||
$st = $pdo->prepare("INSERT INTO users (name, username, password_hash, role, first_login, last_login)
|
||||
VALUES (:n,:u,NULL,:r,1,NULL)");
|
||||
$st->execute([':n'=>$name, ':u'=>$username, ':r'=>$role]);
|
||||
} else {
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$st = $pdo->prepare("INSERT INTO users (name, username, password_hash, role, first_login, last_login)
|
||||
VALUES (:n,:u,:p,:r,0,NULL)");
|
||||
$st->execute([':n'=>$name, ':u'=>$username, ':p'=>$hash, ':r'=>$role]);
|
||||
}
|
||||
|
||||
echo json_encode(['ok'=>true,'id'=>(int)$pdo->lastInsertId()], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
if ($password !== '') {
|
||||
// Admin setzt neues Passwort -> first_login=0
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$st = $pdo->prepare("UPDATE users SET name=:n, username=:u, role=:r, password_hash=:p, first_login=0 WHERE id=:id");
|
||||
$st->execute([':n'=>$name, ':u'=>$username, ':r'=>$role, ':p'=>$hash, ':id'=>$id]);
|
||||
} else {
|
||||
$st = $pdo->prepare("UPDATE users SET name=:n, username=:u, role=:r WHERE id=:id");
|
||||
$st->execute([':n'=>$name, ':u'=>$username, ':r'=>$role, ':id'=>$id]);
|
||||
}
|
||||
|
||||
echo json_encode(['ok'=>true,'id'=>$id], JSON_UNESCAPED_UNICODE);
|
||||
} catch (PDOException $e) {
|
||||
if ((int)($e->errorInfo[1] ?? 0) === 1062) {
|
||||
http_response_code(409);
|
||||
echo json_encode(['ok'=>false,'error'=>'Username ist schon vergeben']); exit;
|
||||
}
|
||||
|
||||
http_response_code(500);
|
||||
echo json_encode(['ok'=>false,'error'=>'DB Fehler']); exit;
|
||||
}
|
||||
Reference in New Issue
Block a user