From 30f8855e20c6a433fcf1386f1062c86c6ec79195 Mon Sep 17 00:00:00 2001 From: ZeroIchiro Date: Fri, 18 Sep 2026 10:16:45 +0000 Subject: [PATCH] Add configurable author price calculator --- CHANGELOG.md | 19 +++++ admin.php | 2 +- author_price.php | 154 ++++++++++++++++++++++++++++++++++++++++ css/admin.css | 4 ++ scripts/author_price.js | 12 ++++ 5 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 author_price.php create mode 100644 scripts/author_price.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8996490 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Änderungsprotokoll + +## 2026-09-18 + +Seit Commit `0eb32fb` wurden folgende Änderungen vorgenommen: + +- Neue geschützte Admin-Seite `author_price.php` für die Berechnung von Autorenpreisen. +- Printbücher können per Dropdown ausgewählt werden. +- Berechnung des Autorenpreises auf Grundlage von Brutto-Preis, 7 % Mehrwertsteuer, Druckkosten und einem frei wählbaren Rabatt. +- Der Rabatt ist standardmäßig mit 85 % vorbelegt und kann zwischen 0 und 100 % eingegeben werden. +- Die Ersparnis pro Exemplar wird separat ausgewiesen. +- Der Autorenrabatt wird als eigener Rechenposten angezeigt. +- Checkbox ergänzt, um alternativ die kumulierte Autorenmarge aller am Buch beteiligten Autoren als Rabatt zu verwenden. +- Das manuelle Rabattfeld wird bei aktivierter Margen-Checkbox deaktiviert. +- Die kumulierte Autorenmarge wird aus allen Buch-Autor-Zuordnungen berechnet. +- JavaScript für die unmittelbare Aktivierung und Deaktivierung des Rabattfelds ergänzt. +- Navigation im Admin-Bereich um den Link zum Autorenpreis-Rechner erweitert. +- CSS für die Anordnung von Rabattfeld und Checkbox ergänzt. +- PHP-, SQL- und Zugriffsschutz-Prüfungen für die neue Funktion durchgeführt. diff --git a/admin.php b/admin.php index b1d57f6..50e1e24 100644 --- a/admin.php +++ b/admin.php @@ -156,7 +156,7 @@ $token = csrf(); ?> Autorenmargen – Admin -

Autorenmargen – Admin

+

Autorenmargen – Admin

Abbrechen
diff --git a/author_price.php b/author_price.php new file mode 100644 index 0000000..2f17e98 --- /dev/null +++ b/author_price.php @@ -0,0 +1,154 @@ +query( + 'SELECT b.id, b.title, b.selling_price, b.printing_costs, b.isbn_print, + COALESCE(SUM(ba.margin_percent), 0) AS author_margin_percent + FROM books b + LEFT JOIN book_authors ba ON ba.book_id = b.id + WHERE b.isbn_print IS NOT NULL AND b.isbn_print <> "" + GROUP BY b.id + ORDER BY b.title' +)->fetchAll(); + +$selectedId = filter_var($_GET['book_id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) ?: null; +$useAuthorMargin = ($_GET['use_author_margin'] ?? '') === '1'; +$discountInput = $_GET['discount_percent'] ?? (string) DEFAULT_AUTHOR_DISCOUNT_PERCENT; +$discountPercent = filter_var($discountInput, FILTER_VALIDATE_FLOAT); +$discountError = $discountPercent === false || $discountPercent < 0 || $discountPercent > 100; +if ($discountError) { + $discountPercent = DEFAULT_AUTHOR_DISCOUNT_PERCENT; +} +$discountRate = (float) $discountPercent / 100; +$retainedRate = 1 - $discountRate; +$selectedBook = null; +foreach ($books as $book) { + if ((int) $book['id'] === $selectedId) { + $selectedBook = $book; + break; + } +} + +$calculation = null; +if ($selectedBook !== null) { + if ($useAuthorMargin) { + $discountPercent = (float) $selectedBook['author_margin_percent']; + $discountError = false; + } + $discountRate = (float) $discountPercent / 100; + $retainedRate = 1 - $discountRate; + $grossPrice = (float) $selectedBook['selling_price']; + $printingCosts = (float) $selectedBook['printing_costs']; + $netPrice = $grossPrice / (1 + AUTHOR_PRICE_VAT_RATE); + $revenueBeforeAuthorDiscount = $netPrice - $printingCosts; + $authorDiscount = $revenueBeforeAuthorDiscount * $discountRate; + $retainedRevenue = $revenueBeforeAuthorDiscount * $retainedRate; + $authorPriceNet = $printingCosts + $retainedRevenue; + $authorPriceVat = $authorPriceNet * AUTHOR_PRICE_VAT_RATE; + $authorPriceGross = $authorPriceNet + $authorPriceVat; + $saving = $grossPrice - $authorPriceGross; + + $calculation = [ + 'gross_price' => $grossPrice, + 'net_price' => $netPrice, + 'printing_costs' => $printingCosts, + 'revenue_before_author_discount' => $revenueBeforeAuthorDiscount, + 'author_discount' => $authorDiscount, + 'discount_percent' => (float) $discountPercent, + 'retained_percent' => $retainedRate * 100, + 'use_author_margin' => $useAuthorMargin, + 'author_margin_percent' => (float) $selectedBook['author_margin_percent'], + 'retained_revenue' => $retainedRevenue, + 'author_price_net' => $authorPriceNet, + 'author_price_vat' => $authorPriceVat, + 'author_price_gross' => $authorPriceGross, + 'saving' => $saving, + ]; +} + +$token = csrf(); +?> + + + + + Autorenpreis-Rechner + + + +
+

Autorenpreis-Rechner

+ +
+
+
+

Buch auswählen

+
+ + +
+
+ + required> +
+ +
+

Der Rabatt muss zwischen 0 und 100 Prozent liegen. Es wurde 85 Prozent verwendet.

+

Hinterlegte Autorenmarge für dieses Buch: %

+
+
+
+ + +
+

Autorenpreis:

+

Berechnung pro Exemplar. Die Zwischenwerte werden ungerundet berechnet und erst für die Anzeige auf Cent gerundet.

+
+ + + + + + + + + + + + + +
Regulärer Brutto-Preis
Regulärer Netto-Preis (Brutto ÷ 1,07)
Abzüglich Druckkosten
Erlös vor Autorenrabatt
Autorenrabatt %
Verbleibender Erlösanteil %
Autorenpreis netto (Druckkosten + verbleibender Anteil)
Mehrwertsteuer 7 % auf Autorenpreis
Autorenpreis brutto
Ersparnis pro Exemplar
+
+
+ +

Das ausgewählte Buch ist nicht als Printbuch vorhanden.

+ +
+ + + diff --git a/css/admin.css b/css/admin.css index 86ee454..ad32e74 100644 --- a/css/admin.css +++ b/css/admin.css @@ -31,6 +31,10 @@ th { background: #e0e7ff; color: #172554; } nav { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; } nav a { color: #fff; } .billing-table input[type=number] { min-width: 7rem; } +.discount-input-row { display: flex; gap: 1rem; align-items: end; flex-wrap: wrap; } +.discount-input-row > div { flex: 1 1 15rem; } +.checkbox-label { display: flex; align-items: center; gap: .45rem; padding-bottom: .65rem; } +.checkbox-label input { width: auto; } .muted { color: #64748b; } .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } @media (max-width: 600px) { .author-row { grid-template-columns: 1fr; } header { align-items: flex-start; flex-direction: column; } } diff --git a/scripts/author_price.js b/scripts/author_price.js new file mode 100644 index 0000000..f569cc9 --- /dev/null +++ b/scripts/author_price.js @@ -0,0 +1,12 @@ +(() => { + const checkbox = document.querySelector('#use-author-margin'); + const discountInput = document.querySelector('#discount-percent'); + if (!checkbox || !discountInput) return; + + const updateDiscountInput = () => { + discountInput.disabled = checkbox.checked; + }; + + checkbox.addEventListener('change', updateDiscountInput); + updateDiscountInput(); +})(); \ No newline at end of file