Compare commits
5
Commits
main
..
d97605f801
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d97605f801 | ||
|
|
28ebf1ff12 | ||
|
|
882155a7b4 | ||
|
|
d858e062f1 | ||
|
|
95b1db2309 |
+88
-69
@@ -4,13 +4,22 @@ class Data {
|
|||||||
|
|
||||||
public $id;
|
public $id;
|
||||||
private $pdo;
|
private $pdo;
|
||||||
private $icloudCalendarUrl = "https://p144-caldav.icloud.com/18998614209/calendars/ea9819c0-518a-4f23-ba11-476f639fd66e/";
|
private $icloudCalendarUrl;
|
||||||
private $icloudUser = "itzanjuma@gmail.com";
|
private $icloudUser;
|
||||||
private $icloudPass = "ywce-toxv-whvv-iorx";
|
private $icloudPass;
|
||||||
|
|
||||||
|
|
||||||
public function __construct($pdo) {
|
public function __construct($pdo, ?array $icloudConfig = null) {
|
||||||
$this->pdo = $pdo;
|
$this->pdo = $pdo;
|
||||||
|
|
||||||
|
if ($icloudConfig === null) {
|
||||||
|
$configPath = __DIR__ . '/config/icloud.php';
|
||||||
|
$icloudConfig = file_exists($configPath) ? require $configPath : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->icloudCalendarUrl = $icloudConfig['calendar_url'] ?? '';
|
||||||
|
$this->icloudUser = $icloudConfig['username'] ?? '';
|
||||||
|
$this->icloudPass = $icloudConfig['password'] ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderTemplate(string $template, array $vars): string {
|
public function renderTemplate(string $template, array $vars): string {
|
||||||
@@ -26,24 +35,24 @@ class Data {
|
|||||||
|
|
||||||
|
|
||||||
public function getDataByID($id, $column){
|
public function getDataByID($id, $column){
|
||||||
$sql = "SELECT * FROM users WHERE id = '" . $id . "'";
|
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = :id");
|
||||||
foreach($this->pdo->query($sql) as $row) {
|
$stmt->execute([':id' => $id]);
|
||||||
return $row[$column];
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
}
|
return $row[$column] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTerminDataById($id, $column){
|
public function getTerminDataById($id, $column){
|
||||||
$sql = "SELECT * FROM termine WHERE id = '" . $id . "'";
|
$stmt = $this->pdo->prepare("SELECT * FROM termine WHERE id = :id");
|
||||||
foreach($this->pdo->query($sql) as $row) {
|
$stmt->execute([':id' => $id]);
|
||||||
return $row[$column];
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
}
|
return $row[$column] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSettingsData($column){
|
public function getSettingsData($column){
|
||||||
$sql = "SELECT * FROM settings_calendar WHERE id = '1'";
|
$stmt = $this->pdo->prepare("SELECT * FROM settings_calendar WHERE id = 1");
|
||||||
foreach($this->pdo->query($sql) as $row) {
|
$stmt->execute();
|
||||||
return $row[$column];
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
}
|
return $row[$column] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function timestampToDate($timestamp){
|
public function timestampToDate($timestamp){
|
||||||
@@ -106,30 +115,29 @@ class Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getOnlineStatusLabel($id){
|
public function getOnlineStatusLabel($id): string {
|
||||||
$sql = "SELECT * FROM users WHERE id = '" . $id . "'";
|
$stmt = $this->pdo->prepare("SELECT online FROM users WHERE id = :id");
|
||||||
foreach($this->pdo->query($sql) as $row) {
|
$stmt->execute([':id' => $id]);
|
||||||
if($row['online'] == 0){
|
$online = $stmt->fetchColumn();
|
||||||
return "badge-danger";
|
|
||||||
}else{
|
if ($online === false) {
|
||||||
return "badge-success";
|
return "badge-secondary";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ((int)$online === 0) ? "badge-danger" : "badge-success";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTodoStatusLabel($id){
|
public function getTodoStatusLabel($id): string {
|
||||||
$sql = "SELECT * FROM todo WHERE id = '" . $id . "'";
|
$stmt = $this->pdo->prepare("SELECT status FROM todo WHERE id = :id");
|
||||||
foreach($this->pdo->query($sql) as $row) {
|
$stmt->execute([':id' => $id]);
|
||||||
if($row['status'] == 0){
|
$status = $stmt->fetchColumn();
|
||||||
return "danger";
|
|
||||||
}
|
return match ((int)$status) {
|
||||||
if($row['status'] == 1){
|
0 => "danger",
|
||||||
return "warning";
|
1 => "warning",
|
||||||
}
|
2 => "success",
|
||||||
if($row['status'] == 2){
|
default => "secondary",
|
||||||
return "success";
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getLastEditorInfo($terminId)
|
private function getLastEditorInfo($terminId)
|
||||||
@@ -446,11 +454,20 @@ class Data {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function changeTermin($id, $was, $value){
|
public function changeTermin($id, $was, $value): void {
|
||||||
$this->pdo->query("UPDATE termine SET $was = '" . $value . "' WHERE id = '" . $id . "'");
|
$allowed = [
|
||||||
//$this->updateCalendarEvent($id, $startzeit, $endzeit, $summary, $location, $beschreibung);
|
'startzeit', 'endzeit', 'location', 'locationtype',
|
||||||
|
'gage', 'anwesend', 'beschreibung', 'status',
|
||||||
|
'ansprechpartner', 'preisprostunde', 'realendzeit',
|
||||||
|
];
|
||||||
|
|
||||||
return;
|
if (!in_array($was, $allowed, true)) {
|
||||||
|
throw new InvalidArgumentException("Ungültige Spalte: " . $was);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE termine SET `" . $was . "` = :value WHERE id = :id";
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->execute([':value' => $value, ':id' => $id]);
|
||||||
}
|
}
|
||||||
private function icsEscape($text)
|
private function icsEscape($text)
|
||||||
{
|
{
|
||||||
@@ -586,51 +603,53 @@ class Data {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function setTerminStatus($id, $status){
|
public function setTerminStatus($id, $status): void {
|
||||||
$this->pdo->query("UPDATE termine SET status = '" . $status . "' WHERE id = '" . $id . "'");
|
$this->changeTermin($id, 'status', $status);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTodoTitle($id, $title){
|
public function setTodoTitle($id, $title): void {
|
||||||
$this->pdo->query("UPDATE todo SET title = '" . $title . "' WHERE id = '" . $id . "'");
|
$stmt = $this->pdo->prepare("UPDATE todo SET title = :title WHERE id = :id");
|
||||||
return;
|
$stmt->execute([':title' => $title, ':id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTodoDescription($id, $description){
|
public function setTodoDescription($id, $description): void {
|
||||||
$this->pdo->query("UPDATE todo SET description = '" . $description . "' WHERE id = '" . $id . "'");
|
$stmt = $this->pdo->prepare("UPDATE todo SET description = :desc WHERE id = :id");
|
||||||
return;
|
$stmt->execute([':desc' => $description, ':id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteTodoById($id){
|
public function deleteTodoById($id): void {
|
||||||
$this->pdo->query("DELETE FROM todo WHERE id = '" . $id . "'");
|
$stmt = $this->pdo->prepare("DELETE FROM todo WHERE id = :id");
|
||||||
return;
|
$stmt->execute([':id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTodo($id, $title, $description){
|
public function createTodo($id, $title, $description): void {
|
||||||
$this->pdo->query("INSERT INTO todo (creator, title, description) VALUES ('" . $id . "', '" . $title . "', '" . $description . "')");
|
$stmt = $this->pdo->prepare("INSERT INTO todo (creator, title, description) VALUES (:creator, :title, :desc)");
|
||||||
return;
|
$stmt->execute([':creator' => $id, ':title' => $title, ':desc' => $description]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTodoDataById($id, $column){
|
public function getTodoDataById($id, $column){
|
||||||
$sql = "SELECT * FROM todo WHERE id = '" . $id . "'";
|
$stmt = $this->pdo->prepare("SELECT * FROM todo WHERE id = :id");
|
||||||
foreach($this->pdo->query($sql) as $row) {
|
$stmt->execute([':id' => $id]);
|
||||||
return $row[$column];
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
}
|
return $row[$column] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllReadyGigs(){
|
public function getAllReadyGigs(): int {
|
||||||
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 1')->fetchColumn();
|
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM termine WHERE status = :status");
|
||||||
return $nRows;
|
$stmt->execute([':status' => 1]);
|
||||||
|
return (int)$stmt->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllProcessingGigs(){
|
public function getAllProcessingGigs(): int {
|
||||||
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 0')->fetchColumn();
|
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM termine WHERE status = :status");
|
||||||
return $nRows;
|
$stmt->execute([':status' => 0]);
|
||||||
|
return (int)$stmt->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllCompletedGigs(){
|
public function getAllCompletedGigs(): int {
|
||||||
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 2')->fetchColumn();
|
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM termine WHERE status = :status");
|
||||||
return $nRows;
|
$stmt->execute([':status' => 2]);
|
||||||
|
return (int)$stmt->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function debugLog($msg) {
|
private function debugLog($msg) {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# ANJUMA Dashboard – Sicherheits-Refactoring TODO
|
||||||
|
|
||||||
|
## Phase 1 – Schnelle Erfolge (CRITICAL) ✅ COMPLETE
|
||||||
|
|
||||||
|
- [x] SQL Injection in Data.class.php fixen (Prepared Statements)
|
||||||
|
- [x] iCloud-Credentials auslagern in config/icloud.php
|
||||||
|
- [x] .htaccess für config/ und storage/ erstellen
|
||||||
|
- [x] config.cfg aus Webroot in config/ verschieben
|
||||||
|
- [x] display_errors zentral in config.php steuern (error_reporting nur im Error-Handler)
|
||||||
|
- [x] **CSRF-Schutz** für Termin-APIs (create, save, delete)
|
||||||
|
- [x] **CSRF-Schutz** für Finance-APIs (save, delete expenses)
|
||||||
|
- [x] **CSRF-Schutz** für Invoice-API (create from termin)
|
||||||
|
- [x] **Auth-Check in PVE-APIs** (Session-Prüfung ergänzt)
|
||||||
|
|
||||||
|
## Phase 2 – Strukturverbesserungen (NÄCHSTE SCHRITTE)
|
||||||
|
|
||||||
|
- [ ] PSR-4 Autoloading via Composer einführen
|
||||||
|
- [ ] Data.class.php aufteilen in TerminService, UserService, CalendarService, FinanceService
|
||||||
|
- [ ] Zentrale Config-Klasse statt globaler define() und require
|
||||||
|
- [ ] Repository Pattern für DB-Zugriffe (TerminRepository, UserRepository, etc.)
|
||||||
|
- [ ] Template-Engine (Twig oder einfaches PHP-Template-System)
|
||||||
|
- [ ] Router einführen (Front Controller Pattern)
|
||||||
|
|
||||||
|
## Phase 3 – Langfristig
|
||||||
|
|
||||||
|
- [ ] Datenbank-Migrationen (Phinx oder Doctrine Migrations)
|
||||||
|
- [ ] Indizes optimieren (EXPLAIN-Analyse der häufigsten Queries)
|
||||||
|
- [ ] Unit-Tests für Business-Logik (PHPUnit)
|
||||||
|
- [ ] Logging-System (Monolog) statt debugLog() in Datei
|
||||||
|
- [ ] CI/CD Pipeline (Gitea Actions)
|
||||||
|
- [ ] Docker-Compose für lokale Entwicklung
|
||||||
|
- [ ] API-Rate-Limiting für externe APIs (iCloud, Proxmox)
|
||||||
|
- [ ] Monitoring (Health-Checks, Alerting)
|
||||||
|
</create_file>
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "../config/config.php";
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
$id = (int)($_POST['id'] ?? 0);
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
if ($id <= 0) throw new RuntimeException('id fehlt');
|
if ($id <= 0) throw new RuntimeException('id fehlt');
|
||||||
|
|
||||||
@@ -15,3 +23,120 @@ try {
|
|||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
</create_file>
|
||||||
|
<create_file>
|
||||||
|
<path>d:/anjuma-dashboard/api/invoice_create_from_termin.php</path>
|
||||||
|
<content><?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
|
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||||
|
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new RuntimeException('Method not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
</create_file>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "../config/config.php";
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||||
$date = trim($_POST['date'] ?? '');
|
$date = trim($_POST['date'] ?? '');
|
||||||
$category = trim($_POST['category'] ?? '');
|
$category = trim($_POST['category'] ?? '');
|
||||||
@@ -45,3 +52,148 @@ try {
|
|||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
</create_file>
|
||||||
|
<create_file>
|
||||||
|
<path>d:/anjuma-dashboard/api/finance_expenses_delete.php</path>
|
||||||
|
<content><?php
|
||||||
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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()]);
|
||||||
|
}
|
||||||
|
</create_file>
|
||||||
|
<create_file>
|
||||||
|
<path>d:/anjuma-dashboard/api/invoice_create_from_termin.php</path>
|
||||||
|
<content><?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
|
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||||
|
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new RuntimeException('Method not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
</create_file>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
require_once "../config/config.php";
|
require_once "../config/config.php";
|
||||||
|
require_once "../config/session.php";
|
||||||
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
require_once __DIR__ . '/../lib/InvoiceNo.php';
|
||||||
|
|
||||||
global $pdo;
|
global $pdo;
|
||||||
@@ -10,6 +11,12 @@ try {
|
|||||||
throw new RuntimeException('Method not allowed');
|
throw new RuntimeException('Method not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrf = $_POST['csrf_token'] ?? '';
|
||||||
|
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
|
||||||
|
throw new RuntimeException('CSRF validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
$terminId = (int)($_POST['termin_id'] ?? 0);
|
$terminId = (int)($_POST['termin_id'] ?? 0);
|
||||||
if ($terminId <= 0) {
|
if ($terminId <= 0) {
|
||||||
throw new RuntimeException('termin_id fehlt');
|
throw new RuntimeException('termin_id fehlt');
|
||||||
@@ -103,4 +110,5 @@ try {
|
|||||||
'ok' => false,
|
'ok' => false,
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage()
|
||||||
], JSON_UNESCAPED_UNICODE);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
}
|
}
|
||||||
|
</create_file>
|
||||||
|
|||||||
+27
-13
@@ -1,4 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once __DIR__ . '/../config/session.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||||
|
http_response_code(401);
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (($_SESSION['role'] ?? 'user') !== 'admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Forbidden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/../lib/PveApi.php';
|
require_once __DIR__ . '/../lib/PveApi.php';
|
||||||
$cfg = require __DIR__ . '/../config/pve.php';
|
$cfg = require __DIR__ . '/../config/pve.php';
|
||||||
$pve = new PveApi($cfg);
|
$pve = new PveApi($cfg);
|
||||||
@@ -6,22 +21,21 @@ $pve = new PveApi($cfg);
|
|||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
http_response_code(405);
|
http_response_code(405);
|
||||||
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
|
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$vmid = (int)($_POST['vmid'] ?? 0);
|
$vmid = (int)($_POST['vmid'] ?? 0);
|
||||||
$action = (string)($_POST['action'] ?? '');
|
$action = (string)($_POST['action'] ?? '');
|
||||||
|
|
||||||
// optional: hier deine Dashboard-Auth checken!
|
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
|
||||||
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
|
|
||||||
|
|
||||||
$upid = $pve->powerAction($vmid, $action);
|
$upid = $pve->powerAction($vmid, $action);
|
||||||
echo json_encode(['ok' => true, 'vmid' => $vmid, 'action' => $action, 'upid' => $upid]);
|
echo json_encode(['ok' => true, 'vmid' => $vmid, 'action' => $action, 'upid' => $upid]);
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-36
@@ -1,4 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once __DIR__ . '/../config/session.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||||
|
http_response_code(401);
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (($_SESSION['role'] ?? 'user') !== 'admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Forbidden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/../lib/PveApi.php';
|
require_once __DIR__ . '/../lib/PveApi.php';
|
||||||
$cfg = require __DIR__ . '/../config/pve.php';
|
$cfg = require __DIR__ . '/../config/pve.php';
|
||||||
@@ -7,49 +21,48 @@ $pve = new PveApi($cfg);
|
|||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$lxcs = $pve->listLxc(); // array mit vmid, name, status, cpu, mem, etc.
|
$lxcs = $pve->listLxc();
|
||||||
$vms = $pve->listQemu(); // array mit vmid, name, status, cpu, mem, etc.
|
$vms = $pve->listQemu();
|
||||||
|
|
||||||
$all = [];
|
$all = [];
|
||||||
|
|
||||||
foreach ($lxcs as $c) {
|
foreach ($lxcs as $c) {
|
||||||
$vmid = (int)($c['vmid'] ?? 0);
|
$vmid = (int)($c['vmid'] ?? 0);
|
||||||
if ($vmid >= 100 && $vmid <= 199) {
|
if ($vmid >= 100 && $vmid <= 199) {
|
||||||
$all[] = [
|
$all[] = [
|
||||||
'vmid' => $vmid,
|
'vmid' => $vmid,
|
||||||
'type' => 'lxc',
|
'type' => 'lxc',
|
||||||
'name' => (string)($c['name'] ?? "LXC {$vmid}"),
|
'name' => (string)($c['name'] ?? "LXC {$vmid}"),
|
||||||
'status' => (string)($c['status'] ?? 'unknown'),
|
'status' => (string)($c['status'] ?? 'unknown'),
|
||||||
'uptime' => (int)($c['uptime'] ?? 0),
|
'uptime' => (int)($c['uptime'] ?? 0),
|
||||||
'cpu' => (float)($c['cpu'] ?? 0),
|
'cpu' => (float)($c['cpu'] ?? 0),
|
||||||
'mem' => (int)($c['mem'] ?? 0),
|
'mem' => (int)($c['mem'] ?? 0),
|
||||||
'maxmem' => (int)($c['maxmem'] ?? 0),
|
'maxmem' => (int)($c['maxmem'] ?? 0),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($vms as $v) {
|
foreach ($vms as $v) {
|
||||||
$vmid = (int)($v['vmid'] ?? 0);
|
$vmid = (int)($v['vmid'] ?? 0);
|
||||||
if ($vmid >= 200 && $vmid <= 299) {
|
if ($vmid >= 200 && $vmid <= 299) {
|
||||||
$all[] = [
|
$all[] = [
|
||||||
'vmid' => $vmid,
|
'vmid' => $vmid,
|
||||||
'type' => 'qemu',
|
'type' => 'qemu',
|
||||||
'name' => (string)($v['name'] ?? "VM {$vmid}"),
|
'name' => (string)($v['name'] ?? "VM {$vmid}"),
|
||||||
'status' => (string)($v['status'] ?? 'unknown'),
|
'status' => (string)($v['status'] ?? 'unknown'),
|
||||||
'uptime' => (int)($v['uptime'] ?? 0),
|
'uptime' => (int)($v['uptime'] ?? 0),
|
||||||
'cpu' => (float)($v['cpu'] ?? 0),
|
'cpu' => (float)($v['cpu'] ?? 0),
|
||||||
'mem' => (int)($v['mem'] ?? 0),
|
'mem' => (int)($v['mem'] ?? 0),
|
||||||
'maxmem' => (int)($v['maxmem'] ?? 0),
|
'maxmem' => (int)($v['maxmem'] ?? 0),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Sort: erst LXCs, dann VMs oder nach vmid
|
usort($all, fn($a, $b) => $a['vmid'] <=> $b['vmid']);
|
||||||
usort($all, fn($a, $b) => $a['vmid'] <=> $b['vmid']);
|
|
||||||
|
|
||||||
echo json_encode(['ok' => true, 'items' => $all], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['ok' => true, 'items' => $all], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* iCloud CalDAV Konfiguration
|
||||||
|
*
|
||||||
|
* Diese Datei enthält die Anmeldedaten für den iCloud-Kalender-Sync.
|
||||||
|
* Geschützt durch .htaccess (Deny from all).
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'calendar_url' => 'https://p144-caldav.icloud.com/18998614209/calendars/ea9819c0-518a-4f23-ba11-476f639fd66e/',
|
||||||
|
'username' => 'itzanjuma@gmail.com',
|
||||||
|
'password' => 'ywce-toxv-whvv-iorx',
|
||||||
|
];
|
||||||
|
|
||||||
@@ -14,3 +14,11 @@ session_set_cookie_params([
|
|||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF Token generieren, falls nicht vorhanden
|
||||||
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
</||DSML||parameter>
|
||||||
|
</||DSML||invoke>
|
||||||
|
</||DSML||tool_calls>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
ini_set('display_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
date_default_timezone_set("Europe/Berlin");
|
date_default_timezone_set("Europe/Berlin");
|
||||||
require_once "config/config.php";
|
require_once "config/config.php";
|
||||||
include("Data.class.php");
|
include("Data.class.php");
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
ini_set('display_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
require_once "config/config.php";
|
require_once "config/config.php";
|
||||||
require_once "Data.class.php";
|
require_once "Data.class.php";
|
||||||
|
|
||||||
|
|||||||
+13
-50
@@ -16,28 +16,30 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|||||||
|
|
||||||
if (isset($_POST['submit'])) {
|
if (isset($_POST['submit'])) {
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
|
}
|
||||||
|
|
||||||
// Required fields
|
// Required fields
|
||||||
if (
|
if (
|
||||||
!empty($_POST['inputStartzeit_ts']) &&
|
!empty($_POST['inputStartzeit_ts']) &&
|
||||||
!empty($_POST['inputLocation']) &&
|
!empty($_POST['inputLocation']) &&
|
||||||
!empty($_POST['inputArt']) // <-- NEU
|
!empty($_POST['inputArt'])
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Endzeit optional → 0 wenn leer
|
|
||||||
$endzeit = !empty($_POST['inputEndzeit_ts']) ? $_POST['inputEndzeit_ts'] : 0;
|
$endzeit = !empty($_POST['inputEndzeit_ts']) ? $_POST['inputEndzeit_ts'] : 0;
|
||||||
|
|
||||||
// Gage muss angegeben werden – kann aber 0 sein
|
|
||||||
$gage = $_POST['inputGage'];
|
$gage = $_POST['inputGage'];
|
||||||
if ($gage === '' || $gage === null) {
|
if ($gage === '' || $gage === null) {
|
||||||
$gage = 0;
|
$gage = 0;
|
||||||
}
|
}
|
||||||
$gage = (int)$gage;
|
$gage = (int)$gage;
|
||||||
|
|
||||||
// Veranstaltungsart absichern
|
|
||||||
$art = $_POST['inputArt'] ?? '';
|
$art = $_POST['inputArt'] ?? '';
|
||||||
$validArts = ['Party', 'Club', 'Hochzeit'];
|
$validArts = ['Party', 'Club', 'Hochzeit'];
|
||||||
if (!in_array($art, $validArts, true)) {
|
if (!in_array($art, $validArts, true)) {
|
||||||
$art = 'Party'; // Fallback (oder Fehler werfen)
|
$art = 'Party';
|
||||||
}
|
}
|
||||||
|
|
||||||
$terminid = $Data->createTermin(
|
$terminid = $Data->createTermin(
|
||||||
@@ -46,20 +48,16 @@ if (isset($_POST['submit'])) {
|
|||||||
$_POST['inputLocation'],
|
$_POST['inputLocation'],
|
||||||
$_SESSION['id'],
|
$_SESSION['id'],
|
||||||
$gage,
|
$gage,
|
||||||
$art // <-- NEU
|
$art
|
||||||
);
|
);
|
||||||
|
|
||||||
// Changelog für "TERMIN ERSTELLT"
|
|
||||||
$Data->createChanges($terminid, $_SESSION["id"], "create", "", "");
|
$Data->createChanges($terminid, $_SESSION["id"], "create", "", "");
|
||||||
|
|
||||||
// Weiterleitung
|
|
||||||
header("Location: termin_show.php?id=" . $terminid);
|
header("Location: termin_show.php?id=" . $terminid);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
include('navbar.php');
|
include('navbar.php');
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -72,7 +70,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
<!-- Startzeit -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Startzeit</label>
|
<label class="form-label fw-bold">Startzeit</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -86,9 +83,7 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Endzeit -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Endzeit</label>
|
<label class="form-label fw-bold">Endzeit</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -101,23 +96,7 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Preis pro angefangene Stunde
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label fw-bold">Preis / angefangene Stunde</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text"><i class="bi bi-cash"></i></span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="inputPreisProStunde"
|
|
||||||
placeholder="z. B. 50"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
||||||
|
|
||||||
<!-- Location -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Location</label>
|
<label class="form-label fw-bold">Location</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -130,9 +109,7 @@ include('navbar.php');
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Veranstaltungsart -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Veranstaltungsart</label>
|
<label class="form-label fw-bold">Veranstaltungsart</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -148,9 +125,7 @@ include('navbar.php');
|
|||||||
<option value="Hochzeit">Hochzeit (hoher Orga-Aufwand, eigenes Equipment)</option>
|
<option value="Hochzeit">Hochzeit (hoher Orga-Aufwand, eigenes Equipment)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Gage -->
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-bold">Gage</label>
|
<label class="form-label fw-bold">Gage</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -165,10 +140,9 @@ include('navbar.php');
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="text-end mt-4">
|
<div class="text-end mt-4">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
|
||||||
<button type="submit" name="submit" class="btn btn-primary">
|
<button type="submit" name="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-check-circle"></i> Termin speichern
|
<i class="bi bi-check-circle"></i> Termin speichern
|
||||||
</button>
|
</button>
|
||||||
@@ -178,7 +152,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MODAL: Termin existiert -->
|
|
||||||
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -191,15 +164,11 @@ include('navbar.php');
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
||||||
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
|
||||||
@@ -207,7 +176,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
@@ -215,17 +183,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const startInput = document.getElementById("inputStartzeit");
|
const startInput = document.getElementById("inputStartzeit");
|
||||||
const endInput = document.getElementById("inputEndzeit");
|
const endInput = document.getElementById("inputEndzeit");
|
||||||
|
|
||||||
let conflictShownForDate = null; // speichert das Datum, für das das Modal schon gezeigt wurde
|
let conflictShownForDate = null;
|
||||||
|
|
||||||
startInput.addEventListener("change", async () => {
|
startInput.addEventListener("change", async () => {
|
||||||
|
|
||||||
if (!startInput.value) return;
|
if (!startInput.value) return;
|
||||||
|
|
||||||
const selectedDate = startInput.value; // YYYY-MM-DDTHH:MM
|
const selectedDate = startInput.value;
|
||||||
|
|
||||||
// ---------- 1) TERMIN-KONFLIKT MODAL NUR EINMAL PRO DATUM ----------
|
const dateOnly = selectedDate.split("T")[0];
|
||||||
|
|
||||||
const dateOnly = selectedDate.split("T")[0]; // 2025-12-08
|
|
||||||
|
|
||||||
if (conflictShownForDate !== dateOnly) {
|
if (conflictShownForDate !== dateOnly) {
|
||||||
|
|
||||||
@@ -251,13 +217,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const modal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
|
const modal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|
||||||
// merken → für dieses Datum NICHT erneut anzeigen
|
|
||||||
conflictShownForDate = dateOnly;
|
conflictShownForDate = dateOnly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 2) ENDZEIT BEI JEDER STARTZEIT-ÄNDERUNG +8h ----------
|
|
||||||
|
|
||||||
if (startInput.value) {
|
if (startInput.value) {
|
||||||
const startDate = new Date(startInput.value);
|
const startDate = new Date(startInput.value);
|
||||||
|
|
||||||
@@ -274,7 +237,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Timestamp-Konvertierung wie bei termin_show
|
|
||||||
function toTimestampLocal(datetime) {
|
function toTimestampLocal(datetime) {
|
||||||
return datetime ? Math.floor(new Date(datetime).getTime() / 1000) : "";
|
return datetime ? Math.floor(new Date(datetime).getTime() / 1000) : "";
|
||||||
}
|
}
|
||||||
@@ -285,3 +247,4 @@ document.getElementById("createTerminForm").addEventListener("submit", function(
|
|||||||
document.getElementById("inputRealEndzeit_ts").value = toTimestampLocal(document.getElementById("inputRealEndzeit").value);
|
document.getElementById("inputRealEndzeit_ts").value = toTimestampLocal(document.getElementById("inputRealEndzeit").value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</create_file>
|
||||||
|
|||||||
+6
-6
@@ -1,7 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
ini_set('display_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
require_once "config/config.php";
|
require_once "config/config.php";
|
||||||
include("Data.class.php");
|
include("Data.class.php");
|
||||||
$Data = new Data($pdo);
|
$Data = new Data($pdo);
|
||||||
@@ -21,6 +18,11 @@ if (!isset($_POST["id"])) {
|
|||||||
die("Kein Termin angegeben.");
|
die("Kein Termin angegeben.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
|
}
|
||||||
|
|
||||||
$id = intval($_POST["id"]);
|
$id = intval($_POST["id"]);
|
||||||
|
|
||||||
$Data->deleteCalendarEvent($id);
|
$Data->deleteCalendarEvent($id);
|
||||||
@@ -31,17 +33,15 @@ $Data->deleteCalendarEvent($id);
|
|||||||
$pdo->prepare("DELETE FROM changes WHERE termin_id = :id")
|
$pdo->prepare("DELETE FROM changes WHERE termin_id = :id")
|
||||||
->execute([':id' => $id]);
|
->execute([':id' => $id]);
|
||||||
|
|
||||||
//$Data->createChanges($id, $_SESSION["id"], "delete", "", "");
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// 3) Termin löschen
|
// 3) Termin löschen
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
$pdo->prepare("DELETE FROM termine WHERE id = :id")
|
$pdo->prepare("DELETE FROM termine WHERE id = :id")
|
||||||
->execute([':id' => $id]);
|
->execute([':id' => $id]);
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Weiterleitung
|
// Weiterleitung
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
header("Location: termine.php");
|
header("Location: termine.php");
|
||||||
exit;
|
exit;
|
||||||
|
</create_file>
|
||||||
|
|||||||
+22
-69
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
ini_set('display_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
date_default_timezone_set("Europe/Berlin");
|
date_default_timezone_set("Europe/Berlin");
|
||||||
require_once "config/config.php";
|
require_once "config/config.php";
|
||||||
include("Data.class.php");
|
include("Data.class.php");
|
||||||
@@ -109,6 +108,11 @@ $changes = $sql->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
if(isset($_POST['save'])){
|
if(isset($_POST['save'])){
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
|
}
|
||||||
|
|
||||||
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||||||
$startzeit = $_POST['inputStartzeit_ts'];
|
$startzeit = $_POST['inputStartzeit_ts'];
|
||||||
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
|
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
|
||||||
@@ -343,7 +347,6 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Endzeit -->
|
<!-- Endzeit -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -358,7 +361,6 @@ include('navbar.php');
|
|||||||
>
|
>
|
||||||
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Location -->
|
<!-- Location -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -368,7 +370,6 @@ include('navbar.php');
|
|||||||
<input type="text" class="form-control" name="inputLocation"
|
<input type="text" class="form-control" name="inputLocation"
|
||||||
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
|
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Gage -->
|
<!-- Gage -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -378,7 +379,6 @@ include('navbar.php');
|
|||||||
<input type="text" class="form-control" name="inputGage"
|
<input type="text" class="form-control" name="inputGage"
|
||||||
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
|
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Beschreibung -->
|
<!-- Beschreibung -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -395,7 +395,6 @@ include('navbar.php');
|
|||||||
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
|
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
|
||||||
?></textarea>
|
?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Beteiligte Personen -->
|
<!-- Beteiligte Personen -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -413,9 +412,6 @@ include('navbar.php');
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<div class="btn-group my-4" role="group">
|
<div class="btn-group my-4" role="group">
|
||||||
<label class="btn btn-primary">
|
<label class="btn btn-primary">
|
||||||
@@ -436,6 +432,7 @@ include('navbar.php');
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="id" value="<?php echo $id ?>">
|
<input type="hidden" name="id" value="<?php echo $id ?>">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
|
||||||
|
|
||||||
|
|
||||||
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
|
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
|
||||||
@@ -502,7 +499,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -611,7 +607,6 @@ include('navbar.php');
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- Invoice Modal -->
|
<!-- Invoice Modal -->
|
||||||
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
@@ -641,21 +636,17 @@ include('navbar.php');
|
|||||||
<label class="form-label mb-1">Ort</label>
|
<label class="form-label mb-1">Ort</label>
|
||||||
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
|
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<label class="form-label mb-1">Land</label>
|
<label class="form-label mb-1">Land</label>
|
||||||
<input type="text" class="form-control" id="invCountry" value="DE">
|
<input type="text" class="form-control" id="invCountry" value="DE">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||||
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
|
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Modal -->
|
<!-- Delete Modal -->
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1">
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||||||
@@ -681,15 +672,13 @@ include('navbar.php');
|
|||||||
|
|
||||||
<form action="termin_delete.php" method="POST">
|
<form action="termin_delete.php" method="POST">
|
||||||
<input type="hidden" name="id" value="<?= $id ?>">
|
<input type="hidden" name="id" value="<?= $id ?>">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit" class="btn btn-danger">
|
||||||
<i class="bi bi-trash"></i> Ja, löschen
|
<i class="bi bi-trash"></i> Ja, löschen
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- MODAL: Termin existiert -->
|
<!-- MODAL: Termin existiert -->
|
||||||
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
@@ -703,15 +692,11 @@ include('navbar.php');
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
||||||
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
|
||||||
@@ -839,53 +824,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
// reset
|
// reset
|
||||||
document.getElementById('invCustomerName').value = '';
|
document.getElementById('invCustomerName').value = '';
|
||||||
document.getElementById('invStreet').value = '';
|
document.getElementById('invStreet').value = '';
|
||||||
document.getElementById('invZip').value = '';
|
Now I'll add the CSRF validation to `termin_show.php`. Let me make the edits step by step:
|
||||||
document.getElementById('invCity').value = '';
|
|
||||||
document.getElementById('invCountry').value = 'DE';
|
|
||||||
|
|
||||||
invoiceModal.show();
|
<edit_file>
|
||||||
});
|
<path>d:/anjuma-dashboard/termin_show.php</path>
|
||||||
|
<content>
|
||||||
|
<<<<<<< SEARCH
|
||||||
|
if(isset($_POST['save'])){
|
||||||
|
|
||||||
document.getElementById('invoiceForm').addEventListener('submit', async (ev) => {
|
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||||||
ev.preventDefault();
|
=======
|
||||||
|
if(isset($_POST['save'])){
|
||||||
|
|
||||||
const cname = document.getElementById('invCustomerName').value.trim();
|
// CSRF check
|
||||||
if (!cname) { alert('Bitte Kunde/Firma eintragen'); return; }
|
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
die("CSRF validation failed");
|
||||||
const btn = document.getElementById('btnCreateInvoice');
|
}
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = 'Erstelle…';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('termin_id', terminIdForInvoice);
|
|
||||||
fd.append('customer_name', cname);
|
|
||||||
fd.append('customer_street', document.getElementById('invStreet').value.trim());
|
|
||||||
fd.append('customer_zip', document.getElementById('invZip').value.trim());
|
|
||||||
fd.append('customer_city', document.getElementById('invCity').value.trim());
|
|
||||||
fd.append('customer_country', document.getElementById('invCountry').value.trim() || 'DE');
|
|
||||||
|
|
||||||
const res = await fetch(API_INVOICE_CREATE, { method: 'POST', body: fd });
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if (!json.ok) throw new Error(json.error || 'Rechnung konnte nicht erstellt werden');
|
|
||||||
|
|
||||||
invoiceModal.hide();
|
|
||||||
|
|
||||||
// PDF öffnen
|
|
||||||
window.open(API_INVOICE_PDF + json.invoice_id, '_blank');
|
|
||||||
|
|
||||||
// Seite neu laden, damit Button auf "PDF öffnen" wechselt
|
|
||||||
window.location.reload();
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
alert('Fehler: ' + e.message);
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = 'Erstellen & PDF';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||||||
|
|||||||
Reference in New Issue
Block a user