Files
Webklar-Kundenbereich/public/buchungen-posten.js
T

110 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Buchungskalender: Posten-Editor fuer die Freigabe einer Anfrage.
summeLive(posten, steuerart) ist ein Spiegel von server/services/unterkunft/preis.js summiere (gleiche Rundung): Anzeige waehrend des Tippens,
verbindlich rechnet der Server. Posten: { text, menge, einzelCent (negativ bei Rabatt), ustProzent }. */
import { euroText, euroZuCent, h } from './shop-dom.js'
export function summeLive(posten, steuerart) {
const bruttoCent = posten.reduce((a, p) => a + p.menge * p.einzelCent, 0)
if (steuerart === 'kleinunternehmer') return { bruttoCent, nettoCent: bruttoCent, ust: [] }
const proSatz = new Map()
for (const p of posten) proSatz.set(p.ustProzent, (proSatz.get(p.ustProzent) || 0) + p.menge * p.einzelCent)
const ust = [...proSatz.entries()]
.sort((a, b) => a[0] - b[0])
.map(([satz, brutto]) => {
const nettoCent = Math.round((brutto * 100) / (100 + satz))
return { satz, nettoCent, steuerCent: brutto - nettoCent }
})
const nettoCent = ust.reduce((a, u) => a + u.nettoCent, 0)
return { bruttoCent, nettoCent, ust }
}
/** Betrag mit Vorzeichen (Rabatt) in Euro-Schreibweise: -3850 ⇒ '-38,50 €'. */
export const euroMitVorzeichen = (cent) => (Number.isInteger(cent) ? (cent < 0 ? `-${euroText(-cent)}` : euroText(cent)) : '')
/** Feldtext ('-38,50', '−38,50 €', '12,5') in Cent mit Vorzeichen; null bei allem Unlesbaren. */
export function euroZuCentMitVorzeichen(text) {
if (typeof text !== 'string') return null
const s = text.trim()
const minus = /^[-−–]/.test(s)
const c = euroZuCent(minus ? s.slice(1) : s)
return c === null ? null : minus ? -c : c
}
const centFeld = (cent) => (Number.isInteger(cent) ? `${cent < 0 ? '-' : ''}${Math.floor(Math.abs(cent) / 100)},${String(Math.abs(cent) % 100).padStart(2, '0')}` : '')
/**
* Editor bauen. => { el, posten() => [{text,menge,einzelCent,ustProzent}] oder null bei Unlesbarem, summe() }.
* steuerart 'kleinunternehmer': keine USt-Spalte (ustProzent 0). aenderung() wird nach jeder Eingabe gerufen.
*/
export function baueEditor(start, steuerart, { aenderung } = {}) {
const klein = steuerart === 'kleinunternehmer'
const zeilen = []
const liste = h('div', { class: 'bk-posten' })
const summenEl = h('div', { class: 'bk-summen', 'data-summen': true, 'aria-live': 'polite' })
const lies = () => {
const ergebnis = []
for (const z of zeilen) {
const einzelCent = euroZuCentMitVorzeichen(String(z.einzel.value))
const menge = Number(String(z.menge.value).trim())
if (einzelCent === null || !Number.isInteger(menge) || menge < 1) return null
ergebnis.push({ text: String(z.text.value).trim(), menge, einzelCent, ustProzent: klein ? 0 : Number(z.ust.value) })
}
return ergebnis
}
const zeichneSumme = () => {
const p = lies()
if (!p) {
summenEl.replaceChildren(h('p', { class: 'sh-fehler' }, 'Bitte prüfe Menge und Preis der Posten.'))
return
}
const s = summeLive(p, steuerart)
summenEl.replaceChildren(
...(klein ? [] : [h('p', null, `Netto ${euroMitVorzeichen(s.nettoCent)}`), ...s.ust.map((u) => h('p', null, `${u.satz} % USt ${euroMitVorzeichen(u.steuerCent)}`))]),
h('p', { class: 'bk-gesamt' }, h('strong', null, `Gesamt ${euroMitVorzeichen(s.bruttoCent)}`)),
...(klein ? [h('p', { class: 'sh-hinweis' }, 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.')] : []))
}
const geaendert = () => {
zeichneSumme()
aenderung?.()
}
function fuegeZeile(p = { text: '', menge: 1, einzelCent: 0, ustProzent: 7 }) {
const z = {
text: h('input', { type: 'text', maxlength: '200', 'aria-label': 'Beschreibung', 'data-posten': 'text' }),
menge: h('input', { type: 'text', inputmode: 'numeric', 'aria-label': 'Menge', 'data-posten': 'menge' }),
einzel: h('input', { type: 'text', inputmode: 'decimal', autocomplete: 'off', 'aria-label': 'Einzelpreis in Euro (Rabatt mit Minus)', 'data-posten': 'einzel' }),
ust: klein ? null : h('select', { 'aria-label': 'Umsatzsteuer', 'data-posten': 'ust' }, [7, 19, 0].map((satz) => h('option', { value: String(satz) }, `${satz} %`))),
}
z.text.value = p.text
z.menge.value = String(p.menge)
z.einzel.value = centFeld(p.einzelCent)
if (z.ust) z.ust.value = String(p.ustProzent)
for (const el of [z.text, z.menge, z.einzel, z.ust]) el?.addEventListener('input', geaendert)
z.ust?.addEventListener('change', geaendert)
z.einzel.addEventListener('change', () => {
const c = euroZuCentMitVorzeichen(String(z.einzel.value))
if (c !== null) z.einzel.value = centFeld(c)
})
z.zeile = h('div', { class: 'bk-posten-zeile', 'data-zeile': true },
z.text, z.menge, z.einzel, z.ust,
h('button', {
type: 'button', class: 'kb-btn-outline', 'data-aktion': 'posten-weg', 'aria-label': 'Posten entfernen',
onclick: () => {
zeilen.splice(zeilen.indexOf(z), 1)
z.zeile.remove()
geaendert()
},
}, 'Entfernen'))
zeilen.push(z)
liste.append(z.zeile)
}
for (const p of start) fuegeZeile(p)
const kopf = h('div', { class: 'bk-posten-kopf', 'aria-hidden': 'true' },
h('span', null, 'Beschreibung'), h('span', null, 'Menge'), h('span', null, 'Einzelpreis €'), klein ? null : h('span', null, 'USt'), h('span'))
const el = h('div', { class: 'bk-editor' }, kopf, liste,
h('button', { type: 'button', class: 'kb-btn-outline', 'data-aktion': 'posten-neu', onclick: () => { fuegeZeile(); geaendert() } }, 'Posten hinzufügen'),
summenEl)
zeichneSumme()
return { el, posten: lies, summe: () => { const p = lies(); return p ? summeLive(p, steuerart) : null } }
}