kleinunternehmer ist ab jetzt null (nicht gewaehlt), lieferzeit leer. istVollstaendig verlangt eine bewusste Wahl (true oder false), der Shop kann sonst nicht geoeffnet werden. Im Reiter Einstellungen gibt es zwei Pflicht-Radiobuttons ohne Vorauswahl; Preise, Artikel und Muster rechnen bei null wie bei Regelbesteuerung. Tests und Fixtures angepasst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
404 lines
21 KiB
JavaScript
404 lines
21 KiB
JavaScript
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { berechne } from '../server/services/shop/preise.js'
|
||
import { euro } from '../server/services/shop/geld.js'
|
||
|
||
const basis = {
|
||
aktiv: true, kurz: '', beschreibung: '', steuersatz: 19, grundpreis: null, bestand: null, varianten: null,
|
||
bilder: [], lieferart: 'beides', widerruf: 'gesetzlich',
|
||
}
|
||
const produkt = (id, extra = {}) => ({ ...basis, id, name: `Artikel ${id}`, preisCent: 1000, ...extra })
|
||
const karte = (...liste) => new Map(liste.map((p) => [p.id, p]))
|
||
const shirt = produkt('shirt', {
|
||
name: 'T-Shirt',
|
||
preisCent: null,
|
||
varianten: {
|
||
name: 'Größe',
|
||
werte: [
|
||
{ id: 'sizeaaaa', name: 'M', preisCent: 2000, bestand: 5 },
|
||
{ id: 'sizebbbb', name: 'L', preisCent: 2200, bestand: null },
|
||
],
|
||
},
|
||
})
|
||
const einstellungen = (extra = {}) => ({
|
||
kleinunternehmer: false,
|
||
versand: { aktiv: true, laender: ['DE', 'AT'], kostenCent: { DE: 490, AT: 990 }, freiAbCent: null },
|
||
abholung: { aktiv: true, adresse: 'Weg 1', hinweis: '' },
|
||
mindestbestellCent: null,
|
||
...extra,
|
||
})
|
||
const rechne = (args) => berechne({ lieferart: 'versand', land: 'DE', einstellungen: einstellungen(), ...args })
|
||
const eine = (p, menge = 1, varianteId) => [{ produktId: p.id, menge, ...(varianteId ? { varianteId } : {}) }]
|
||
|
||
test('einfacher Warenkorb: Summen, Texte, Versand und Steuer', () => {
|
||
const a = produkt('a', { preisCent: 1190 })
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a, 2) })
|
||
assert.equal(r.ok, true)
|
||
assert.equal(r.zwischensummeCent, 2380)
|
||
assert.equal(r.versandCent, 490)
|
||
assert.equal(r.gesamtCent, 2870)
|
||
assert.deepEqual(r.positionen[0], {
|
||
produktId: 'a', varianteId: null, name: 'Artikel a', varianteText: '', menge: 2, einzelCent: 1190, summeCent: 2380,
|
||
steuersatz: 19, widerruf: 'gesetzlich', einzelText: euro(1190), summeText: euro(2380),
|
||
})
|
||
assert.equal(r.texte.zwischensumme, euro(2380))
|
||
assert.equal(r.texte.versand, euro(490))
|
||
assert.equal(r.texte.gesamt, euro(2870))
|
||
// 2870 * 19 / 119 = 458,23 => 458
|
||
assert.deepEqual(r.steuer, [{ satz: 19, betragCent: 458, text: `inkl. 19 % USt: ${euro(458)}` }])
|
||
assert.equal(r.texte.steuerHinweis, `inkl. 19 % USt: ${euro(458)}`)
|
||
assert.equal(r.lieferart, 'versand')
|
||
assert.equal(r.land, 'DE')
|
||
})
|
||
|
||
test('gemischte Steuersaetze: Text mit absteigenden Saetzen, 0 % liefert keinen Eintrag', () => {
|
||
const a = produkt('a', { preisCent: 2000, steuersatz: 19 })
|
||
const b = produkt('b', { preisCent: 700, steuersatz: 7 })
|
||
const c = produkt('c', { preisCent: 500, steuersatz: 0 })
|
||
const e = einstellungen({ versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: 1 } })
|
||
const r = berechne({ produkte: karte(a, b, c), positionen: [...eine(b), ...eine(c), ...eine(a)], lieferart: 'versand', land: 'DE', einstellungen: e })
|
||
assert.equal(r.versandCent, 0)
|
||
assert.equal(r.texte.versand, 'kostenlos')
|
||
assert.deepEqual(r.steuer.map((s) => [s.satz, s.betragCent]), [[19, 319], [7, 46]])
|
||
assert.equal(r.texte.steuerHinweis, `inkl. 19 % USt: ${euro(319)}; inkl. 7 % USt: ${euro(46)}`)
|
||
assert.deepEqual(r.positionen.map((p) => p.produktId), ['b', 'c', 'a'], 'Reihenfolge der Eingabe bleibt')
|
||
})
|
||
|
||
test('Steuer wird je Satz ueber die Summe gerundet, nicht je Position', () => {
|
||
// Einzeln: 3 * round(0,119 * ... ) waere 3 * 0 ; ueber die Summe: 3 * 10 = 30 Cent brutto => 30*19/119 = 4,79 => 5
|
||
const a = produkt('a', { preisCent: 10 })
|
||
const fueller = produkt('fueller', { preisCent: 100, steuersatz: 0 }) // hebt den Gesamtbetrag ueber 0,50 EUR, ohne Steuer
|
||
const e = einstellungen({ abholung: { aktiv: true, adresse: 'x', hinweis: '' } })
|
||
const r = berechne({ produkte: karte(a, fueller), positionen: [...eine(a, 3), ...eine(fueller)], lieferart: 'abholung', einstellungen: e })
|
||
assert.equal(r.steuer[0].betragCent, 5)
|
||
})
|
||
|
||
test('Rundung halb auf an der Grenze', () => {
|
||
// brutto 107 bei 7 % => 107*7/107 = 7 exakt; brutto 50 bei 7 % => 3,271 => 3; brutto 1 bei 19 % => 0,159 => 0 (kein Eintrag)
|
||
const fueller = produkt('fueller', { preisCent: 100, steuersatz: 0 }) // Gesamtbetrag mindestens 0,50 EUR, selbst ohne Steuer
|
||
const abh = (p, n = 1) => berechne({ produkte: karte(p, fueller), positionen: [...eine(p, n), ...eine(fueller)], lieferart: 'abholung', einstellungen: einstellungen() })
|
||
assert.equal(abh(produkt('a', { preisCent: 107, steuersatz: 7 })).steuer[0].betragCent, 7)
|
||
assert.equal(abh(produkt('a', { preisCent: 50, steuersatz: 7 })).steuer[0].betragCent, 3)
|
||
assert.deepEqual(abh(produkt('a', { preisCent: 1, steuersatz: 19 })).steuer, [])
|
||
assert.equal(abh(produkt('a', { preisCent: 1, steuersatz: 19 })).texte.steuerHinweis, '')
|
||
// brutto 5 bei 19 % => 0,798 => 1
|
||
assert.equal(abh(produkt('a', { preisCent: 5, steuersatz: 19 })).steuer[0].betragCent, 1)
|
||
// brutto 30 bei 7 % => 1,962 => 2
|
||
assert.equal(abh(produkt('a', { preisCent: 30, steuersatz: 7 })).steuer[0].betragCent, 2)
|
||
})
|
||
|
||
test('Versand wird anteilig nach Bruttowert auf die Saetze verteilt, Rest-Cent auf den hoechsten Satz', () => {
|
||
const a = produkt('a', { preisCent: 1000, steuersatz: 19 })
|
||
const b = produkt('b', { preisCent: 1000, steuersatz: 7 })
|
||
const e = einstellungen({ versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 501 }, freiAbCent: null } })
|
||
const r = berechne({ produkte: karte(b, a), positionen: [...eine(b), ...eine(a)], lieferart: 'versand', land: 'DE', einstellungen: e })
|
||
// Anteile: floor(501*1000/2000) = 250 je Satz, Rest 1 => 19 %: 1251, 7 %: 1250
|
||
assert.equal(r.gesamtCent, 2501)
|
||
assert.deepEqual(r.steuer.map((s) => [s.satz, s.betragCent]), [
|
||
[19, Math.floor((2 * 1251 * 19 + 119) / (2 * 119))],
|
||
[7, Math.floor((2 * 1250 * 7 + 107) / (2 * 107))],
|
||
])
|
||
assert.equal(r.steuer[0].betragCent, 200)
|
||
assert.equal(r.steuer[1].betragCent, 82)
|
||
})
|
||
|
||
test('Versand bei nur 0-%-Artikeln erzeugt keine Steuer', () => {
|
||
const a = produkt('a', { preisCent: 1000, steuersatz: 0 })
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a) })
|
||
assert.deepEqual(r.steuer, [])
|
||
assert.equal(r.gesamtCent, 1490)
|
||
})
|
||
|
||
test('Kleinunternehmer: keine Steuer, Hinweis nach Paragraph 19', () => {
|
||
const a = produkt('a', { preisCent: 1000, steuersatz: 0 })
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a), einstellungen: einstellungen({ kleinunternehmer: true }) })
|
||
assert.deepEqual(r.steuer, [])
|
||
assert.equal(r.texte.steuerHinweis, 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.')
|
||
// auch wenn ein Artikel (fehlerhaft) einen Satz traegt
|
||
const b = produkt('b', { preisCent: 1000, steuersatz: 19 })
|
||
const r2 = rechne({ produkte: karte(b), positionen: eine(b), einstellungen: einstellungen({ kleinunternehmer: true }) })
|
||
assert.deepEqual(r2.steuer, [])
|
||
})
|
||
|
||
test('Steuerart noch nicht gewählt (null) rechnet wie Regelbesteuerung, nie wie Kleinunternehmer', () => {
|
||
const a = produkt('a', { preisCent: 1190, steuersatz: 19 })
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a), einstellungen: einstellungen({ kleinunternehmer: null }) })
|
||
assert.ok(r.steuer.length > 0)
|
||
assert.doesNotMatch(r.texte.steuerHinweis, /§ 19/)
|
||
})
|
||
|
||
test('Versandkosten: Oesterreich, Schwelle gleich ist kostenlos, knapp darunter nicht', () => {
|
||
const e = einstellungen({ versand: { aktiv: true, laender: ['DE', 'AT'], kostenCent: { DE: 490, AT: 990 }, freiAbCent: 5000 } })
|
||
const p = (cent) => produkt('a', { preisCent: cent })
|
||
const lauf = (cent, land) => {
|
||
const a = p(cent)
|
||
return berechne({ produkte: karte(a), positionen: eine(a), lieferart: 'versand', land, einstellungen: e })
|
||
}
|
||
assert.equal(lauf(2000, 'AT').versandCent, 990)
|
||
assert.equal(lauf(2000, 'AT').texte.versand, euro(990))
|
||
assert.equal(lauf(4999, 'DE').versandCent, 490)
|
||
assert.equal(lauf(5000, 'DE').versandCent, 0)
|
||
assert.equal(lauf(5000, 'AT').versandCent, 0)
|
||
assert.equal(lauf(5001, 'DE').texte.versand, 'kostenlos')
|
||
assert.equal(lauf(5000, 'DE').gesamtCent, 5000)
|
||
})
|
||
|
||
test('freiAbCent 0 heisst immer kostenlos', () => {
|
||
const a = produkt('a')
|
||
const e = einstellungen({ versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: 0 } })
|
||
assert.equal(berechne({ produkte: karte(a), positionen: eine(a), lieferart: 'versand', land: 'DE', einstellungen: e }).versandCent, 0)
|
||
})
|
||
|
||
test('Abholung: keine Versandkosten, eigener Text, Land ignoriert', () => {
|
||
const a = produkt('a', { preisCent: 1190 })
|
||
const r = berechne({ produkte: karte(a), positionen: eine(a), lieferart: 'abholung', einstellungen: einstellungen() })
|
||
assert.equal(r.ok, true)
|
||
assert.equal(r.versandCent, 0)
|
||
assert.equal(r.gesamtCent, 1190)
|
||
assert.equal(r.texte.versand, 'Abholung – kostenlos')
|
||
assert.equal(r.lieferart, 'abholung')
|
||
})
|
||
|
||
test('Abholung nur-Abholung-Artikel sind bei Abholung erlaubt', () => {
|
||
const a = produkt('a', { lieferart: 'abholung' })
|
||
const r = berechne({ produkte: karte(a), positionen: eine(a), lieferart: 'abholung', einstellungen: einstellungen() })
|
||
assert.equal(r.ok, true)
|
||
})
|
||
|
||
test('Varianten: Preis und Text der Variante', () => {
|
||
const r = rechne({ produkte: karte(shirt), positionen: eine(shirt, 2, 'sizebbbb') })
|
||
assert.equal(r.ok, true)
|
||
assert.equal(r.positionen[0].varianteId, 'sizebbbb')
|
||
assert.equal(r.positionen[0].varianteText, 'Größe: L')
|
||
assert.equal(r.positionen[0].einzelCent, 2200)
|
||
assert.equal(r.positionen[0].summeCent, 4400)
|
||
})
|
||
|
||
test('Fehler leer: leere, fehlende oder keine Liste', () => {
|
||
const a = produkt('a')
|
||
for (const positionen of [[], undefined, null, 'x', {}]) {
|
||
const r = rechne({ produkte: karte(a), positionen })
|
||
assert.equal(r.ok, false)
|
||
assert.equal(r.fehler.code, 'leer')
|
||
assert.match(r.fehler.text, /Warenkorb/)
|
||
}
|
||
})
|
||
|
||
test('Fehler zu_viele: 51 verschiedene Positionen, aber 50 sind erlaubt und Duplikate zaehlen einmal', () => {
|
||
const liste = Array.from({ length: 51 }, (_, i) => produkt(`p${i}`))
|
||
const positionen = (n) => liste.slice(0, n).map((p) => ({ produktId: p.id, menge: 1 }))
|
||
const r51 = rechne({ produkte: karte(...liste), positionen: positionen(51) })
|
||
assert.equal(r51.ok, false)
|
||
assert.equal(r51.fehler.code, 'zu_viele')
|
||
assert.equal(rechne({ produkte: karte(...liste), positionen: positionen(50) }).ok, true)
|
||
// 51 Eintraege, aber nur 50 verschiedene
|
||
const dup = [...positionen(50), { produktId: 'p0', menge: 1 }]
|
||
const r = rechne({ produkte: karte(...liste), positionen: dup })
|
||
assert.equal(r.ok, true)
|
||
assert.equal(r.positionen.length, 50)
|
||
assert.equal(r.positionen[0].menge, 2)
|
||
})
|
||
|
||
test('Fehler menge: 0, 100, negativ, Dezimal, Text, NaN, fehlend', () => {
|
||
const a = produkt('a')
|
||
for (const menge of [0, 100, -1, 1.5, '2', NaN, Infinity, undefined, null]) {
|
||
const r = rechne({ produkte: karte(a), positionen: [{ produktId: 'a', menge }] })
|
||
assert.equal(r.ok, false, String(menge))
|
||
assert.equal(r.fehler.code, 'menge', String(menge))
|
||
assert.equal(r.fehler.produktId, 'a')
|
||
}
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a, 99) }).ok, true)
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a, 1) }).ok, true)
|
||
})
|
||
|
||
test('Zusammengefasste Menge ueber 99 ist ein Mengenfehler', () => {
|
||
const a = produkt('a')
|
||
const r = rechne({ produkte: karte(a), positionen: [{ produktId: 'a', menge: 60 }, { produktId: 'a', menge: 40 }] })
|
||
assert.equal(r.ok, false)
|
||
assert.equal(r.fehler.code, 'menge')
|
||
assert.equal(r.fehler.produktId, 'a')
|
||
})
|
||
|
||
test('Duplikate gleicher Artikel werden zusammengefasst, Varianten bleiben getrennt', () => {
|
||
const a = produkt('a', { preisCent: 300 })
|
||
const r = rechne({
|
||
produkte: karte(a, shirt),
|
||
positionen: [
|
||
{ produktId: 'a', menge: 1 },
|
||
{ produktId: 'shirt', varianteId: 'sizeaaaa', menge: 1 },
|
||
{ produktId: 'a', menge: 2 },
|
||
{ produktId: 'shirt', varianteId: 'sizebbbb', menge: 1 },
|
||
{ produktId: 'shirt', varianteId: 'sizeaaaa', menge: 1 },
|
||
],
|
||
})
|
||
assert.equal(r.ok, true)
|
||
assert.deepEqual(r.positionen.map((p) => [p.produktId, p.varianteId, p.menge]), [
|
||
['a', null, 3], ['shirt', 'sizeaaaa', 2], ['shirt', 'sizebbbb', 1],
|
||
])
|
||
assert.equal(r.zwischensummeCent, 900 + 4000 + 2200)
|
||
})
|
||
|
||
test('Fehler nicht_verfuegbar: unbekannt, inaktiv, ungueltige Position', () => {
|
||
const a = produkt('a')
|
||
const aus = produkt('aus', { aktiv: false })
|
||
for (const pos of [{ produktId: 'x', menge: 1 }, { produktId: 'aus', menge: 1 }, { produktId: '__proto__', menge: 1 }, { produktId: 'constructor', menge: 1 }]) {
|
||
const r = rechne({ produkte: karte(a, aus), positionen: [pos] })
|
||
assert.equal(r.ok, false)
|
||
assert.equal(r.fehler.code, 'nicht_verfuegbar')
|
||
}
|
||
assert.equal(rechne({ produkte: karte(a), positionen: [null] }).fehler.code, 'nicht_verfuegbar')
|
||
assert.equal(rechne({ produkte: karte(a), positionen: [{ menge: 1 }] }).fehler.code, 'nicht_verfuegbar')
|
||
assert.equal(rechne({ produkte: karte(a), positionen: [{ produktId: 5, menge: 1 }] }).fehler.code, 'nicht_verfuegbar')
|
||
assert.equal(rechne({ produkte: karte(aus), positionen: eine(aus) }).fehler.produktId, 'aus')
|
||
})
|
||
|
||
test('Fehler variante: fehlt, unbekannt, bei Artikel ohne Varianten', () => {
|
||
const a = produkt('a')
|
||
const ohne = rechne({ produkte: karte(shirt), positionen: eine(shirt) })
|
||
assert.equal(ohne.fehler.code, 'variante')
|
||
assert.match(ohne.fehler.text, /Größe/)
|
||
assert.equal(rechne({ produkte: karte(shirt), positionen: eine(shirt, 1, 'gibtsnich') }).fehler.code, 'variante')
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a, 1, 'sizeaaaa') }).fehler.code, 'variante')
|
||
assert.equal(ohne.fehler.produktId, 'shirt')
|
||
assert.equal(rechne({ produkte: karte(shirt), positionen: eine(shirt, 1, 'sizeaaaa') }).ok, true)
|
||
})
|
||
|
||
test('Bestand: unbegrenzt, genau ausreichend, zu wenig mit Restmenge, ausverkauft', () => {
|
||
const frei = produkt('frei', { bestand: null })
|
||
assert.equal(rechne({ produkte: karte(frei), positionen: eine(frei, 99) }).ok, true)
|
||
const a = produkt('a', { bestand: 3 })
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a, 3) }).ok, true)
|
||
const zuwenig = rechne({ produkte: karte(a), positionen: eine(a, 4) })
|
||
assert.equal(zuwenig.ok, false)
|
||
assert.equal(zuwenig.fehler.code, 'ausverkauft')
|
||
assert.equal(zuwenig.fehler.produktId, 'a')
|
||
assert.match(zuwenig.fehler.text, /Artikel a/)
|
||
assert.match(zuwenig.fehler.text, /nur noch 3/)
|
||
const null0 = produkt('n', { bestand: 0 })
|
||
const aus = rechne({ produkte: karte(null0), positionen: eine(null0) })
|
||
assert.equal(aus.fehler.code, 'ausverkauft')
|
||
assert.match(aus.fehler.text, /Artikel n/)
|
||
assert.match(aus.fehler.text, /ausverkauft/)
|
||
})
|
||
|
||
test('Bestand: gilt fuer die zusammengefasste Menge', () => {
|
||
const a = produkt('a', { bestand: 3 })
|
||
const r = rechne({ produkte: karte(a), positionen: [{ produktId: 'a', menge: 2 }, { produktId: 'a', menge: 2 }] })
|
||
assert.equal(r.fehler.code, 'ausverkauft')
|
||
})
|
||
|
||
test('Reservierungen verringern den Bestand (Artikel und Variante)', () => {
|
||
const a = produkt('a', { bestand: 5 })
|
||
const res = new Map([['a', 3]])
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a, 2), reserviert: res }).ok, true)
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a, 3), reserviert: res })
|
||
assert.equal(r.fehler.code, 'ausverkauft')
|
||
assert.match(r.fehler.text, /nur noch 2/)
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a, 1), reserviert: new Map([['a', 7]]) }).fehler.code, 'ausverkauft')
|
||
|
||
const vres = new Map([['shirt:sizeaaaa', 4]])
|
||
assert.equal(rechne({ produkte: karte(shirt), positionen: eine(shirt, 1, 'sizeaaaa'), reserviert: vres }).ok, true)
|
||
const rv = rechne({ produkte: karte(shirt), positionen: eine(shirt, 2, 'sizeaaaa'), reserviert: vres })
|
||
assert.equal(rv.fehler.code, 'ausverkauft')
|
||
assert.match(rv.fehler.text, /T-Shirt/)
|
||
assert.match(rv.fehler.text, /Größe: M/)
|
||
// unbegrenzte Variante bleibt unberuehrt, andere Schluessel stoeren nicht
|
||
assert.equal(rechne({ produkte: karte(shirt), positionen: eine(shirt, 50, 'sizebbbb'), reserviert: new Map([['shirt:sizebbbb', 99], ['shirt', 99]]) }).ok, true)
|
||
})
|
||
|
||
test('Variantenbestand wird je Variante geprueft', () => {
|
||
const r = rechne({ produkte: karte(shirt), positionen: eine(shirt, 6, 'sizeaaaa') })
|
||
assert.equal(r.fehler.code, 'ausverkauft')
|
||
assert.match(r.fehler.text, /nur noch 5/)
|
||
})
|
||
|
||
test('Fehler lieferart: unbekannt, Versand aus, Abholung aus', () => {
|
||
const a = produkt('a')
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), lieferart: 'post' }).fehler.code, 'lieferart')
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), lieferart: undefined }).fehler.code, 'lieferart')
|
||
const keinVersand = einstellungen({ versand: { aktiv: false, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null } })
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), einstellungen: keinVersand }).fehler.code, 'lieferart')
|
||
const keineAbholung = einstellungen({ abholung: { aktiv: false, adresse: '', hinweis: '' } })
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), lieferart: 'abholung', einstellungen: keineAbholung }).fehler.code, 'lieferart')
|
||
// Abholung geht, obwohl Versand aus ist
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), lieferart: 'abholung', einstellungen: einstellungen({ versand: { aktiv: false, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null } }) }).ok, true)
|
||
})
|
||
|
||
test('Fehler land: nicht geliefert, fehlend, ungueltig', () => {
|
||
const a = produkt('a')
|
||
const nurDe = einstellungen({ versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null } })
|
||
for (const land of ['AT', 'CH', '', undefined, null, 'de']) {
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a), land, einstellungen: nurDe })
|
||
assert.equal(r.fehler.code, 'land', String(land))
|
||
}
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), land: 'AT' }).ok, true)
|
||
})
|
||
|
||
test('Fehler nur_abholung: Versand mit Nur-Abholung-Artikel', () => {
|
||
const a = produkt('a')
|
||
const abholer = produkt('abholer', { lieferart: 'abholung' })
|
||
const r = rechne({ produkte: karte(a, abholer), positionen: [...eine(a), ...eine(abholer)] })
|
||
assert.equal(r.ok, false)
|
||
assert.equal(r.fehler.code, 'nur_abholung')
|
||
assert.equal(r.fehler.produktId, 'abholer')
|
||
assert.match(r.fehler.text, /Artikel abholer/)
|
||
})
|
||
|
||
test('Fehler mindestbestellwert: Grenze gleich ist erlaubt, Versandkosten zaehlen nicht mit', () => {
|
||
const a = produkt('a', { preisCent: 1000 })
|
||
const e = (cent) => einstellungen({ mindestbestellCent: cent })
|
||
const r = rechne({ produkte: karte(a), positionen: eine(a), einstellungen: e(1001) })
|
||
assert.equal(r.ok, false)
|
||
assert.equal(r.fehler.code, 'mindestbestellwert')
|
||
assert.ok(r.fehler.text.includes(euro(1001)))
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), einstellungen: e(1000) }).ok, true)
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), einstellungen: e(null) }).ok, true)
|
||
// gilt auch bei Abholung
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), lieferart: 'abholung', einstellungen: e(1001) }).fehler.code, 'mindestbestellwert')
|
||
})
|
||
|
||
test('Fehler mindestbetrag: Gesamtbetrag unter 0,50 EUR (Stripe-Minimum) wird abgelehnt, 0,50 EUR genau ist erlaubt; Versandkosten zaehlen mit', () => {
|
||
const klein = produkt('klein', { preisCent: 30 })
|
||
const r = rechne({ produkte: karte(klein), positionen: eine(klein), lieferart: 'abholung' })
|
||
assert.equal(r.ok, false)
|
||
assert.equal(r.fehler.code, 'mindestbetrag')
|
||
assert.equal(r.fehler.text, 'Der Bestellwert muss mindestens 0,50 € betragen.')
|
||
const genau = produkt('genau', { preisCent: 50 })
|
||
assert.equal(rechne({ produkte: karte(genau), positionen: eine(genau), lieferart: 'abholung' }).ok, true)
|
||
const knapp = produkt('knapp', { preisCent: 49 })
|
||
assert.equal(rechne({ produkte: karte(knapp), positionen: eine(knapp), lieferart: 'abholung' }).fehler.code, 'mindestbetrag')
|
||
// mit Versandkosten ueber der Grenze
|
||
const e = einstellungen({ versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null } })
|
||
assert.equal(rechne({ produkte: karte(klein), positionen: eine(klein), lieferart: 'versand', land: 'DE', einstellungen: e }).ok, true)
|
||
})
|
||
|
||
test('Reihenfolge der Pruefungen: Artikelfehler vor Lieferart, Lieferart vor Mindestbestellwert', () => {
|
||
const a = produkt('a', { bestand: 0 })
|
||
assert.equal(rechne({ produkte: karte(a), positionen: eine(a), lieferart: 'post' }).fehler.code, 'ausverkauft')
|
||
const b = produkt('b')
|
||
assert.equal(rechne({ produkte: karte(b), positionen: eine(b), lieferart: 'post', einstellungen: einstellungen({ mindestbestellCent: 99999 }) }).fehler.code, 'lieferart')
|
||
})
|
||
|
||
test('Eingaben werden nicht veraendert und das Ergebnis teilt keine Referenzen', () => {
|
||
const a = produkt('a', { bestand: 10 })
|
||
const positionen = [{ produktId: 'a', menge: 2 }, { produktId: 'a', menge: 1 }]
|
||
const e = einstellungen()
|
||
const vorher = JSON.stringify({ a, positionen, e })
|
||
const r1 = rechne({ produkte: karte(a), positionen, einstellungen: e })
|
||
assert.equal(JSON.stringify({ a, positionen, e }), vorher)
|
||
const r2 = rechne({ produkte: karte(a), positionen, einstellungen: e })
|
||
assert.deepEqual(r1, r2)
|
||
assert.notEqual(r1.positionen, r2.positionen)
|
||
})
|
||
|
||
test('Gesamtsumme ist immer ganzzahlig und gleich Zwischensumme plus Versand', () => {
|
||
const a = produkt('a', { preisCent: 333, steuersatz: 7 })
|
||
const b = produkt('b', { preisCent: 777, steuersatz: 19 })
|
||
const r = rechne({ produkte: karte(a, b), positionen: [{ produktId: 'a', menge: 7 }, { produktId: 'b', menge: 3 }], land: 'AT' })
|
||
assert.ok(Number.isInteger(r.gesamtCent))
|
||
assert.equal(r.gesamtCent, r.zwischensummeCent + r.versandCent)
|
||
assert.equal(r.zwischensummeCent, 333 * 7 + 777 * 3)
|
||
const summe = r.steuer.reduce((s, x) => s + x.betragCent, 0)
|
||
assert.ok(summe < r.gesamtCent)
|
||
})
|