Buchungskalender: Preise je Personenzahl (Server)
Jede Staffel darf personenCent tragen (Preis pro Nacht für 1 bis k Personen, k = 2 bis 10), ohne Schemaänderung im JSON-Feld staffeln. pruefePreise prüft Länge, Werte und Gleichheit über alle Staffeln und setzt preisProNachtCent = personenCent[0]. berechnePreis wählt die Spalte der Gästezahl (Posten mit Personenzahl), bei mehr Gästen als Spalten gilt die letzte plus Zusatzperson-Aufpreis. preiseVollstaendig prüft die Konsistenz. Ohne personenCent rechnet alles wie bisher. Tests mit Mimis Preismatrix, Doku in unterkunft-api.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b264d7f734
commit
a4ade5f649
5 files changed
+379
-12
No files matched your search
@@ -1,16 +1,31 @@
|
||||
// Preisrechnung Buchungskalender: reine Funktionen, keine I/O. Betraege in Cent.
|
||||
//
|
||||
// Preismodelle (preise.staffeln, Staffel = hoechste abNaechten <= Naechte, sie gilt fuer alle Naechte der Buchung):
|
||||
// - pauschal (ohne personenCent): preisProNachtCent gilt fuer die ersten personenInklusive Gaeste, jede weitere Person
|
||||
// kostet aufpreisPersonNachtCent je Nacht (eigener Posten "Zusatzperson").
|
||||
// - je Personenzahl (alle Staffeln mit personenCent gleicher Laenge k, 2 bis 10): personenCent[p - 1] ist der Preis pro
|
||||
// Nacht fuer die ganze Unterkunft bei p Gaesten. personenInklusive wird dann ignoriert; bei mehr als k Gaesten gilt
|
||||
// personenCent[k - 1] plus der Posten "Zusatzperson" (aufpreisPersonNachtCent) fuer die Gaeste ueber k. Der Server
|
||||
// haelt preisProNachtCent === personenCent[0] (pruefePreise), damit aeltere Leser weiter rechnen koennen.
|
||||
// Der Direktrabatt gilt in beiden Modellen auf Uebernachtung und Zusatzpersonen, nicht auf die Reinigung.
|
||||
import { config } from '../../config.js'
|
||||
import { naechte } from './datum.js'
|
||||
|
||||
const SAETZE = [0, 7, 19]
|
||||
|
||||
/** Staffel: hoechste abNaechten <= naechte gilt fuer alle Naechte. */
|
||||
function staffelPreis(staffeln, n) {
|
||||
/** Grenzen der Preise je Personenzahl: Laenge k von personenCent (1 bis k Gaeste) und Wert in Cent. */
|
||||
export const PERSONEN_PREISE_MIN = 2
|
||||
export const PERSONEN_PREISE_MAX = 10
|
||||
export const NACHT_CENT_MIN = 100
|
||||
export const NACHT_CENT_MAX = 100000
|
||||
|
||||
/** Die Staffel, die fuer n Naechte gilt (hoechste abNaechten <= n), sonst null. */
|
||||
function waehleStaffel(staffeln, n) {
|
||||
let best = null
|
||||
for (const s of staffeln || []) {
|
||||
if (s.abNaechten <= n && (!best || s.abNaechten > best.abNaechten)) best = s
|
||||
}
|
||||
return best ? best.preisProNachtCent : 0
|
||||
return best
|
||||
}
|
||||
|
||||
export function berechnePreis({ preise, von, bis, personen }) {
|
||||
@@ -19,10 +34,23 @@ export function berechnePreis({ preise, von, bis, personen }) {
|
||||
const satzUe = klein ? 0 : preise.ustUebernachtung
|
||||
const satzRe = klein ? 0 : preise.ustReinigung
|
||||
const posten = []
|
||||
const nachtPreis = staffelPreis(preise.staffeln, n)
|
||||
posten.push({ text: `Übernachtung, ${n} Nächte`, menge: n, einzelCent: nachtPreis, ustProzent: satzUe })
|
||||
const staffel = waehleStaffel(preise.staffeln, n)
|
||||
const pc = staffel && Array.isArray(staffel.personenCent) && staffel.personenCent.length > 0 ? staffel.personenCent : null
|
||||
let nachtPreis
|
||||
let inklusive
|
||||
if (pc) {
|
||||
// je Personenzahl: die Spalte der Gaestezahl (hoechstens die letzte), der Rest ist Zusatzperson
|
||||
const gaeste = Number.isFinite(Number(personen)) && Number(personen) >= 1 ? Math.floor(Number(personen)) : 1
|
||||
inklusive = Math.min(gaeste, pc.length)
|
||||
nachtPreis = pc[inklusive - 1]
|
||||
posten.push({ text: `Übernachtung, ${n} Nächte, ${inklusive} ${inklusive === 1 ? 'Person' : 'Personen'}`, menge: n, einzelCent: nachtPreis, ustProzent: satzUe })
|
||||
} else {
|
||||
nachtPreis = staffel ? staffel.preisProNachtCent : 0
|
||||
inklusive = Number(preise.personenInklusive)
|
||||
posten.push({ text: `Übernachtung, ${n} Nächte`, menge: n, einzelCent: nachtPreis, ustProzent: satzUe })
|
||||
}
|
||||
let basis = n * nachtPreis
|
||||
const extra = Math.max(0, Number(personen) - Number(preise.personenInklusive))
|
||||
const extra = Math.max(0, Number(personen) - inklusive)
|
||||
if (extra > 0 && preise.aufpreisPersonNachtCent > 0) {
|
||||
posten.push({
|
||||
text: `Zusatzperson${extra > 1 ? 'en' : ''} (${extra}), ${n} Nächte`,
|
||||
@@ -96,12 +124,29 @@ export function formatEuro(cent) {
|
||||
return `${neg ? '−' : ''}${euro},${rest} €`
|
||||
}
|
||||
|
||||
/** Preise je Personenzahl: entweder keine Staffel hat personenCent, oder alle gleich lang (2 bis 10) mit gueltigen Werten und preisProNachtCent === personenCent[0]. */
|
||||
function personenPreiseKonsistent(staffeln) {
|
||||
const hat = (s) => s.personenCent !== undefined && s.personenCent !== null
|
||||
if (!staffeln.some(hat)) return true
|
||||
const k = staffeln[0].personenCent?.length
|
||||
return staffeln.every(
|
||||
(s) =>
|
||||
Array.isArray(s.personenCent) &&
|
||||
s.personenCent.length === k &&
|
||||
k >= PERSONEN_PREISE_MIN &&
|
||||
k <= PERSONEN_PREISE_MAX &&
|
||||
s.personenCent.every((c) => Number.isInteger(c) && c >= NACHT_CENT_MIN && c <= NACHT_CENT_MAX) &&
|
||||
s.preisProNachtCent === s.personenCent[0],
|
||||
)
|
||||
}
|
||||
|
||||
export function preiseVollstaendig(preise) {
|
||||
if (!preise || !Array.isArray(preise.staffeln) || preise.staffeln.length === 0) return false
|
||||
const ab = preise.staffeln.map((s) => s && s.abNaechten)
|
||||
if (!ab.every((a) => Number.isInteger(a) && a > 0)) return false
|
||||
if (new Set(ab).size !== ab.length || !ab.includes(1)) return false
|
||||
if (!preise.staffeln.every((s) => Number.isInteger(s.preisProNachtCent) && s.preisProNachtCent > 0)) return false
|
||||
if (!personenPreiseKonsistent(preise.staffeln)) return false
|
||||
if (!['regel', 'kleinunternehmer'].includes(preise.steuerart)) return false
|
||||
if (!Number.isInteger(preise.personenInklusive) || preise.personenInklusive < 1) return false
|
||||
const nn = (v) => Number.isInteger(v) && v >= 0
|
||||
|
||||
Reference in new issue
Block a user