Files
PackControl/pirp/public/invoice_new.php

225 lines
8.9 KiB
PHP

<?php
require_once __DIR__ . '/../src/config.php';
require_once __DIR__ . '/../src/auth.php';
require_once __DIR__ . '/../src/invoice_functions.php';
require_once __DIR__ . '/../src/customer_functions.php';
require_once __DIR__ . '/../src/db.php';
require_once __DIR__ . '/../src/pdf_functions.php';
require_once __DIR__ . '/../src/icons.php';
require_login();
$pdo = get_db();
$settings = get_settings();
$customers = get_customers();
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$customer_id = (int)($_POST['customer_id'] ?? 0);
$invoice_date = $_POST['invoice_date'] ?: date('Y-m-d');
$service_date = $_POST['service_date'] ?: null;
$notes_internal = $_POST['notes_internal'] ?? '';
if ($customer_id <= 0) {
$error = 'Bitte einen Kunden auswählen.';
} else {
$vat_mode = $settings['vat_mode'] ?? 'klein';
$vat_rate = (float)($settings['default_vat_rate'] ?? 19.0);
$items = [];
$total_net = 0.0;
$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 && $price >= 0) {
$line_net = $qty * $price;
$total_net += $line_net;
$items[] = [
'position_no' => count($items) + 1,
'description' => $desc,
'quantity' => $qty,
'unit_price' => $price,
];
}
}
if (empty($items)) {
$error = 'Bitte mindestens eine Position ausfüllen.';
} else {
if ($vat_mode === 'normal') {
$total_vat = round($total_net * $vat_rate / 100, 2);
} else {
$total_vat = 0.0;
}
$total_gross = $total_net + $total_vat;
$pdo->beginTransaction();
try {
$invoice_number = generate_invoice_number();
$stmt = $pdo->prepare("INSERT INTO invoices
(invoice_number, customer_id, invoice_date, service_date, vat_mode, vat_rate,
payment_terms, notes_internal, total_net, total_vat, total_gross, paid)
VALUES (:in, :cid, :idate, :sdate, :vm, :vr, :pt, :ni, :tn, :tv, :tg, FALSE)
RETURNING id");
$stmt->execute([
':in' => $invoice_number,
':cid' => $customer_id,
':idate'=> $invoice_date,
':sdate'=> $service_date,
':vm' => $vat_mode,
':vr' => $vat_rate,
':pt' => $settings['payment_terms'] ?? null,
':ni' => $notes_internal,
':tn' => $total_net,
':tv' => $total_vat,
':tg' => $total_gross,
]);
$invoice_id = $stmt->fetchColumn();
$stmtItem = $pdo->prepare("INSERT INTO invoice_items
(invoice_id, position_no, description, quantity, unit_price, vat_rate)
VALUES (:iid, :pn, :d, :q, :up, :vr)");
foreach ($items as $it) {
$stmtItem->execute([
':iid' => $invoice_id,
':pn' => $it['position_no'],
':d' => $it['description'],
':q' => $it['quantity'],
':up' => $it['unit_price'],
':vr' => $vat_rate,
]);
}
$pdo->commit();
// PDF sofort archivieren (GoBD-konform)
archive_invoice_pdf($invoice_id);
header('Location: ' . url_for('invoice_pdf.php?id=' . $invoice_id));
exit;
} catch (Exception $e) {
$pdo->rollBack();
$error = 'Fehler beim Speichern der Rechnung: ' . $e->getMessage();
}
}
}
}
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Neue Rechnung</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>
`;
tbody.appendChild(tr);
}
// Enter springt zum nächsten Feld statt Formular abzuschicken
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
form.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA' && e.target.type !== 'submit') {
e.preventDefault();
const inputs = Array.from(form.querySelectorAll('input, select, textarea, button[type="submit"]'));
const currentIndex = inputs.indexOf(e.target);
if (currentIndex > -1 && currentIndex < inputs.length - 1) {
inputs[currentIndex + 1].focus();
}
}
});
});
</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') ?>" class="active">Neue Rechnung</a>
<a href="<?= url_for('recurring.php') ?>">Abo-Rechnungen</a>
</div>
<?php if ($error): ?><p class="error"><?= htmlspecialchars($error) ?></p><?php endif; ?>
<form method="post">
<label>Kunde:
<select name="customer_id" required>
<option value="">-- wählen --</option>
<?php foreach ($customers as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</label>
<div class="flex-row">
<label>Rechnungsdatum:
<input type="date" name="invoice_date" value="<?= htmlspecialchars(date('Y-m-d')) ?>">
</label>
<label>Leistungsdatum:
<input type="date" name="service_date">
</label>
</div>
<h2>Positionen</h2>
<table class="list">
<thead>
<tr>
<th>Pos.</th>
<th>Beschreibung</th>
<th>Menge</th>
<th>Einzelpreis (netto)</th>
</tr>
</thead>
<tbody id="items-body">
<?php for ($i = 0; $i < 3; $i++): ?>
<tr>
<td><?= $i+1 ?></td>
<td><input type="text" name="item_desc[<?= $i ?>]" size="40"></td>
<td><input type="number" step="0.01" name="item_qty[<?= $i ?>]" value="1"></td>
<td><input type="number" step="0.01" name="item_price[<?= $i ?>]" value="0.00"></td>
</tr>
<?php endfor; ?>
</tbody>
</table>
<button type="button" onclick="addRow()">Position hinzufügen</button>
<label>Interne Notizen (nicht auf Rechnung):
<textarea name="notes_internal" rows="3"></textarea>
</label>
<button type="submit">Rechnung speichern &amp; PDF anzeigen</button>
</form>
</main>
<script src="assets/command-palette.js"></script>
</body>
</html>