278 lines
12 KiB
PHP
278 lines
12 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../src/config.php';
|
|
require_once __DIR__ . '/../src/auth.php';
|
|
require_once __DIR__ . '/../src/db.php';
|
|
require_once __DIR__ . '/../src/invoice_functions.php';
|
|
require_once __DIR__ . '/../src/customer_functions.php';
|
|
require_once __DIR__ . '/../src/recurring_functions.php';
|
|
require_once __DIR__ . '/../src/icons.php';
|
|
require_login();
|
|
|
|
$settings = get_settings();
|
|
$customers = get_customers();
|
|
$error = '';
|
|
$msg = '';
|
|
|
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
|
$template = null;
|
|
$items = [];
|
|
|
|
if ($id) {
|
|
$template = get_recurring_template($id);
|
|
if (!$template) {
|
|
header('Location: ' . url_for('recurring.php'));
|
|
exit;
|
|
}
|
|
$items = get_recurring_template_items($id);
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$data = [
|
|
'template_name' => trim($_POST['template_name'] ?? ''),
|
|
'customer_id' => (int)($_POST['customer_id'] ?? 0),
|
|
'interval_type' => $_POST['interval_type'] ?? 'monthly',
|
|
'start_date' => $_POST['start_date'] ?? date('Y-m-d'),
|
|
'end_date' => $_POST['end_date'] ?: null,
|
|
'next_due_date' => $_POST['next_due_date'] ?? date('Y-m-d'),
|
|
'vat_mode' => $settings['vat_mode'] ?? 'klein',
|
|
'vat_rate' => (float)($settings['default_vat_rate'] ?? 19.0),
|
|
'is_active' => !empty($_POST['is_active']),
|
|
'notes_internal' => $_POST['notes_internal'] ?? ''
|
|
];
|
|
|
|
// Validierung
|
|
if (empty($data['template_name'])) {
|
|
$error = 'Bitte einen Namen eingeben.';
|
|
} elseif ($data['customer_id'] <= 0) {
|
|
$error = 'Bitte einen Kunden auswählen.';
|
|
} else {
|
|
// Positionen sammeln
|
|
$newItems = [];
|
|
$count = isset($_POST['item_desc']) ? count($_POST['item_desc']) : 0;
|
|
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$desc = trim($_POST['item_desc'][$i] ?? '');
|
|
$qty = (float)($_POST['item_qty'][$i] ?? 0);
|
|
$price = (float)($_POST['item_price'][$i] ?? 0);
|
|
|
|
if ($desc !== '' && $qty > 0) {
|
|
$newItems[] = [
|
|
'position_no' => count($newItems) + 1,
|
|
'description' => $desc,
|
|
'quantity' => $qty,
|
|
'unit_price' => $price
|
|
];
|
|
}
|
|
}
|
|
|
|
if (empty($newItems)) {
|
|
$error = 'Bitte mindestens eine Position ausfüllen.';
|
|
} else {
|
|
$template_id = save_recurring_template($id, $data);
|
|
save_recurring_template_items($template_id, $newItems);
|
|
|
|
header('Location: ' . url_for('recurring.php'));
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title><?= $id ? 'Abo-Vorlage bearbeiten' : 'Neue Abo-Vorlage' ?></title>
|
|
<link rel="stylesheet" href="assets/style.css">
|
|
<script>
|
|
function addRow() {
|
|
const tbody = document.getElementById('items-body');
|
|
const index = tbody.children.length;
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${index+1}</td>
|
|
<td><input type="text" name="item_desc[${index}]" size="40"></td>
|
|
<td><input type="number" step="0.01" name="item_qty[${index}]" value="1"></td>
|
|
<td><input type="number" step="0.01" name="item_price[${index}]" value="0.00"></td>
|
|
<td><button type="button" onclick="this.closest('tr').remove()">X</button></td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>PIRP</h1>
|
|
<nav>
|
|
<a href="<?= url_for('index.php') ?>"><?= icon_dashboard() ?>Dashboard</a>
|
|
<a href="<?= url_for('invoices.php') ?>" class="active"><?= icon_invoices() ?>Rechnungen</a>
|
|
<a href="<?= url_for('customers.php') ?>"><?= icon_customers() ?>Kunden</a>
|
|
<a href="<?= url_for('expenses.php') ?>"><?= icon_expenses() ?>Ausgaben</a>
|
|
<a href="<?= url_for('belegarchiv.php') ?>"><?= icon_archive() ?>Belege</a>
|
|
<a href="<?= url_for('journal.php') ?>"><?= icon_journal() ?>Journal</a>
|
|
<a href="<?= url_for('euer.php') ?>"><?= icon_euer() ?>EÜR</a>
|
|
<a href="<?= url_for('settings.php') ?>"><?= icon_settings() ?>Einstellungen</a>
|
|
<a href="<?= url_for('logout.php') ?>"><?= icon_logout() ?>Logout (<?= htmlspecialchars($_SESSION['username'] ?? '') ?>)</a>
|
|
<span class="cmd-k-hint" onclick="document.dispatchEvent(new KeyboardEvent('keydown',{key:'k',ctrlKey:true}))"><kbd>Ctrl+K</kbd></span>
|
|
</nav>
|
|
</header>
|
|
<main>
|
|
<div class="module-subnav">
|
|
<a href="<?= url_for('invoices.php') ?>">Übersicht</a>
|
|
<a href="<?= url_for('invoice_new.php') ?>">Neue Rechnung</a>
|
|
<a href="<?= url_for('recurring.php') ?>" class="active">Abo-Rechnungen</a>
|
|
</div>
|
|
<?php if ($error): ?><p class="error"><?= htmlspecialchars($error) ?></p><?php endif; ?>
|
|
|
|
<form method="post">
|
|
<section>
|
|
<h2>Abo-Details</h2>
|
|
<div>
|
|
<label>Name der Vorlage:
|
|
<input type="text" name="template_name"
|
|
value="<?= htmlspecialchars($template['template_name'] ?? '') ?>" required>
|
|
</label>
|
|
|
|
<label>Kunde:
|
|
<select name="customer_id" required>
|
|
<option value="">-- wählen --</option>
|
|
<?php foreach ($customers as $c): ?>
|
|
<option value="<?= $c['id'] ?>"
|
|
<?= ($template['customer_id'] ?? 0) == $c['id'] ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($c['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
|
|
<label>Intervall:
|
|
<select name="interval_type" required>
|
|
<option value="monthly" <?= ($template['interval_type'] ?? '') === 'monthly' ? 'selected' : '' ?>>
|
|
Monatlich
|
|
</option>
|
|
<option value="quarterly" <?= ($template['interval_type'] ?? '') === 'quarterly' ? 'selected' : '' ?>>
|
|
Quartalsweise (alle 3 Monate)
|
|
</option>
|
|
<option value="yearly" <?= ($template['interval_type'] ?? '') === 'yearly' ? 'selected' : '' ?>>
|
|
Jährlich
|
|
</option>
|
|
</select>
|
|
</label>
|
|
|
|
<div class="flex-row">
|
|
<label>Startdatum:
|
|
<input type="date" name="start_date"
|
|
value="<?= htmlspecialchars($template['start_date'] ?? date('Y-m-d')) ?>" required>
|
|
</label>
|
|
<label>Enddatum (optional):
|
|
<input type="date" name="end_date"
|
|
value="<?= htmlspecialchars($template['end_date'] ?? '') ?>">
|
|
</label>
|
|
<label>Nächste Fälligkeit:
|
|
<input type="date" name="next_due_date"
|
|
value="<?= htmlspecialchars($template['next_due_date'] ?? date('Y-m-d')) ?>" required>
|
|
</label>
|
|
</div>
|
|
|
|
<label>
|
|
<input type="checkbox" name="is_active" value="1"
|
|
<?= ($template['is_active'] ?? true) ? 'checked' : '' ?>>
|
|
Aktiv (Rechnungen werden generiert)
|
|
</label>
|
|
|
|
<label>Interne Notizen:
|
|
<textarea name="notes_internal" rows="2"><?= htmlspecialchars($template['notes_internal'] ?? '') ?></textarea>
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Positionen</h2>
|
|
<div>
|
|
<table class="list" id="items-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:5%">Pos.</th>
|
|
<th>Beschreibung</th>
|
|
<th style="width:12%">Menge</th>
|
|
<th style="width:15%">Einzelpreis (netto)</th>
|
|
<th style="width:5%"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="items-body">
|
|
<?php
|
|
// Bestehende Positionen oder leere Zeilen
|
|
$displayItems = !empty($items) ? $items : [
|
|
['description' => '', 'quantity' => 1, 'unit_price' => 0],
|
|
['description' => '', 'quantity' => 1, 'unit_price' => 0],
|
|
['description' => '', 'quantity' => 1, 'unit_price' => 0]
|
|
];
|
|
foreach ($displayItems as $i => $item):
|
|
?>
|
|
<tr>
|
|
<td><?= $i + 1 ?></td>
|
|
<td><input type="text" name="item_desc[<?= $i ?>]"
|
|
value="<?= htmlspecialchars($item['description']) ?>" size="40"></td>
|
|
<td><input type="number" step="0.01" name="item_qty[<?= $i ?>]"
|
|
value="<?= number_format($item['quantity'], 2, '.', '') ?>"></td>
|
|
<td><input type="number" step="0.01" name="item_price[<?= $i ?>]"
|
|
value="<?= number_format($item['unit_price'], 2, '.', '') ?>"></td>
|
|
<td><button type="button" onclick="this.closest('tr').remove()">X</button></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<p>
|
|
<button type="button" onclick="addRow()" class="button-secondary">Position hinzufügen</button>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<p>
|
|
<button type="submit">Speichern</button>
|
|
<a href="<?= url_for('recurring.php') ?>" class="button-secondary">Abbrechen</a>
|
|
</p>
|
|
</form>
|
|
|
|
<?php if ($id): ?>
|
|
<section>
|
|
<h2>Generierte Rechnungen</h2>
|
|
<div>
|
|
<?php $log = get_recurring_log($id); ?>
|
|
<?php if (empty($log)): ?>
|
|
<p>Noch keine Rechnungen generiert.</p>
|
|
<?php else: ?>
|
|
<table class="list">
|
|
<thead>
|
|
<tr>
|
|
<th>Datum</th>
|
|
<th>Fälligkeit</th>
|
|
<th>Rechnungsnr.</th>
|
|
<th>Betrag</th>
|
|
<th>Aktion</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($log as $entry): ?>
|
|
<tr>
|
|
<td><?= date('d.m.Y H:i', strtotime($entry['generated_at'])) ?></td>
|
|
<td><?= date('d.m.Y', strtotime($entry['due_date'])) ?></td>
|
|
<td><?= htmlspecialchars($entry['invoice_number'] ?? '-') ?></td>
|
|
<td><?= $entry['total_gross'] ? number_format($entry['total_gross'], 2, ',', '.') . ' €' : '-' ?></td>
|
|
<td>
|
|
<?php if ($entry['invoice_id']): ?>
|
|
<a href="<?= url_for('invoice_pdf.php?id=' . $entry['invoice_id']) ?>" target="_blank">PDF</a>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
</main>
|
|
<script src="assets/command-palette.js"></script>
|
|
</body>
|
|
</html>
|