diff --git a/public/shop-tab-einstellungen.js b/public/shop-tab-einstellungen.js index c9fbdc0..b6c6002 100644 --- a/public/shop-tab-einstellungen.js +++ b/public/shop-tab-einstellungen.js @@ -44,7 +44,11 @@ export async function render(panel, ctx = {}) { const telefon = h('input', { type: 'tel', maxlength: 40, autocomplete: 'tel' }) const ustId = textfeld({ maxlength: 20, placeholder: 'DE123456789' }) - const kleinunternehmer = h('input', { type: 'checkbox' }) + // Steuerart: keine Vorauswahl, der Haendler muss sich entscheiden (null = noch nicht gewaehlt) + const steuerJa = h('input', { type: 'radio', name: 'sh-kleinunternehmer', value: 'ja', required: true }) + const steuerNein = h('input', { type: 'radio', name: 'sh-kleinunternehmer', value: 'nein', required: true }) + steuerJa.dataset.feld = 'kleinunternehmer' + steuerNein.dataset.feld = 'kleinunternehmer' const versandAktiv = h('input', { type: 'checkbox' }) const laender = auswahl(VERSAND_ZIELE) @@ -100,7 +104,8 @@ export async function render(panel, ctx = {}) { email.value = text(a.email) telefon.value = text(a.telefon) ustId.value = text(a.ustId) - kleinunternehmer.checked = s.kleinunternehmer !== false + steuerJa.checked = s.kleinunternehmer === true + steuerNein.checked = s.kleinunternehmer === false versandAktiv.checked = v.aktiv !== false laender.value = Array.isArray(v.laender) && v.laender.includes('AT') ? 'DE,AT' : 'DE' kostenDE.value = centZuEuro(kosten.DE) @@ -147,8 +152,12 @@ export async function render(panel, ctx = {}) { feld('anbieter.email', 'E-Mail-Adresse', email), feld('anbieter.telefon', 'Telefon (freiwillig)', telefon), feld('anbieter.ustId', 'USt-IdNr. (freiwillig)', ustId, 'Zum Beispiel DE123456789. Lass das Feld leer, wenn du keine hast.')), - abschnitt('Steuer', null, - hakenFeld('kleinunternehmer', kleinunternehmer, 'Ich bin Kleinunternehmer nach § 19 UStG', 'Dann steht im Shop der Hinweis, dass keine Umsatzsteuer berechnet wird. Ohne Haken gelten deine Preise inklusive Umsatzsteuer.')), + abschnitt('Steuer', 'Pflichtangabe: Bitte wähle, wie bei dir die Umsatzsteuer behandelt wird. Ohne diese Angabe kannst du den Shop nicht öffnen.', + h('fieldset', { class: 'sh-feld sh-radiogruppe', 'aria-required': 'true' }, + h('legend', null, 'Umsatzsteuer'), + h('label', { class: 'sh-haken' }, steuerJa, h('span', null, 'Ich bin Kleinunternehmer nach § 19 UStG', h('small', { class: 'sh-hinweis' }, 'Dann steht im Shop der Hinweis, dass keine Umsatzsteuer berechnet wird.'))), + h('label', { class: 'sh-haken' }, steuerNein, h('span', null, 'Ich weise Umsatzsteuer aus', h('small', { class: 'sh-hinweis' }, 'Dann gelten deine Preise inklusive Umsatzsteuer; den Steuersatz wählst du je Artikel.'))), + h('p', { class: 'sh-fehler', 'data-fehler': 'kleinunternehmer', role: 'alert' }))), abschnitt('Versand', null, hakenFeld('versand.aktiv', versandAktiv, 'Ich versende Bestellungen'), versandDetails), @@ -206,7 +215,7 @@ export async function render(panel, ctx = {}) { telefon: telefon.value.trim(), ustId: ustId.value.trim(), }, - kleinunternehmer: kleinunternehmer.checked, + kleinunternehmer: steuerJa.checked ? true : steuerNein.checked ? false : null, versand: { aktiv, laender: mitAt ? ['DE', 'AT'] : ['DE'], kostenCent, freiAbCent }, abholung: { aktiv: abholungAktiv.checked, adresse: abholAdresse.value.trim(), hinweis: abholHinweis.value.trim() }, lieferzeit: lieferzeit.value.trim(), diff --git a/public/shop.css b/public/shop.css index c7ac33d..b7779e5 100644 --- a/public/shop.css +++ b/public/shop.css @@ -79,6 +79,8 @@ .sh-haken > span { font-weight: 600; } .sh-haken small { display: block; margin-top: 2px; font-weight: 400; } .sh-feld-haken { margin-top: 4px; } +.sh-radiogruppe { border: 0; padding: 0; margin: 0 0 8px; min-width: 0; } +.sh-radiogruppe legend { padding: 0; font-weight: 600; font-size: 14px; } .sh-frage-box { display: grid; gap: 10px; margin: 12px 0; padding: 14px 16px; border-radius: 12px; border: 1px solid var(--kb-purple-line); background: var(--kb-purple-soft); } .sh-frage-box:focus { outline: none; } diff --git a/server/services/shop/einstellungen.js b/server/services/shop/einstellungen.js index 109ea93..1168e02 100644 --- a/server/services/shop/einstellungen.js +++ b/server/services/shop/einstellungen.js @@ -6,7 +6,8 @@ import { withLocks } from '../keyedLock.js' * Shop-Einstellungen (Plan 2026-10-05-webshop.md, Task C2; shopConfig.einstellungen als JSON-Text). * Normalisieren ist rein: Eingabe => { einstellungen } mit frischem, vollstaendigem Objekt oder { fehler: { 'feld.pfad': 'Text' } } * (Texte in Du-Form, das Portal spricht den Haendler an). Leere Pflichtangaben sind beim Normalisieren erlaubt, damit der Haendler - * Schritt fuer Schritt speichern kann; ob alles da ist, sagt istVollstaendig(). Geld ist immer ganze Cent. + * Schritt fuer Schritt speichern kann; ob alles da ist, sagt istVollstaendig() (u. a. muss kleinunternehmer true oder false sein; + * null = noch nicht gewaehlt, rechnet wie false). Geld ist immer ganze Cent. */ const ZAHLUNGSARTEN = ['card', 'klarna', 'sepa_debit'] @@ -26,10 +27,11 @@ const tief = (wert) => { export const STANDARD_EINSTELLUNGEN = tief({ shopname: '', anbieter: { name: '', strasse: '', plz: '', ort: '', land: 'DE', email: '', telefon: '', ustId: '' }, - kleinunternehmer: true, + // null = noch nicht gewaehlt: der Haendler muss sich bewusst entscheiden (istVollstaendig verlangt true oder false) + kleinunternehmer: null, versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null }, abholung: { aktiv: false, adresse: '', hinweis: '' }, - lieferzeit: '2–4 Werktage', + lieferzeit: '', zahlungsarten: ['card'], akzent: '#111111', mindestbestellCent: null, @@ -245,6 +247,8 @@ export function istVollstaendig(einstellungen) { const r = normalisiereEinstellungen(einstellungen) if (r.fehler) return false const e = r.einstellungen + // Die Steuerart muss bewusst gewaehlt sein (kein Standardwert): sonst stuenden unrichtige Steuerangaben im Shop + if (typeof e.kleinunternehmer !== 'boolean') return false return [e.shopname, e.anbieter.name, e.anbieter.strasse, e.anbieter.plz, e.anbieter.ort, e.anbieter.email, e.lieferzeit].every((s) => s !== '') } diff --git a/server/services/shop/kontext.js b/server/services/shop/kontext.js index c70f963..2fcdba7 100644 --- a/server/services/shop/kontext.js +++ b/server/services/shop/kontext.js @@ -215,7 +215,7 @@ export async function bereitschaft(ctx, { produkte } = {}) { ok: istVollstaendig(shop.einstellungen), text: istVollstaendig(shop.einstellungen) ? 'Deine Shop-Angaben sind vollständig.' - : 'Bitte vervollständige deine Shop-Angaben (Shopname, Anbieter mit Anschrift und E-Mail, Lieferzeit).', + : 'Bitte vervollständige deine Shop-Angaben (Shopname, Anbieter mit Anschrift und E-Mail, Steuerart, Lieferzeit).', }, { id: 'rechtstexte', diff --git a/test/publicShop.test.js b/test/publicShop.test.js index f95e9d6..186b29c 100644 --- a/test/publicShop.test.js +++ b/test/publicShop.test.js @@ -58,6 +58,8 @@ const VORHER = { const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', akzent: '#aa3300', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '0631 123', ustId: 'DE123456789' }, diff --git a/test/publicShopKasse.test.js b/test/publicShopKasse.test.js index e1d9895..d5ffb49 100644 --- a/test/publicShopKasse.test.js +++ b/test/publicShopKasse.test.js @@ -58,6 +58,8 @@ const VORHER = { mode: config.shop.mode, pilot: [...config.shop.pilotEmails], ba const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, abholung: { aktiv: true, adresse: 'Hauptstraße 5, 67655 Kaiserslautern', hinweis: 'Mo–Fr 8–12 Uhr' }, diff --git a/test/publicShopWiderruf.test.js b/test/publicShopWiderruf.test.js index 277c23f..96b0a32 100644 --- a/test/publicShopWiderruf.test.js +++ b/test/publicShopWiderruf.test.js @@ -52,6 +52,8 @@ const VORHER = { mode: config.shop.mode, pilot: [...config.shop.pilotEmails], ba const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, } diff --git a/test/shopAbschluss.test.js b/test/shopAbschluss.test.js index 9a67620..7f36423 100644 --- a/test/shopAbschluss.test.js +++ b/test/shopAbschluss.test.js @@ -51,6 +51,8 @@ const PORTAL = 'https://project.webklar.com' const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, abholung: { aktiv: true, adresse: 'Hauptstraße 5, 67655 Kaiserslautern', hinweis: 'Mo–Fr 8–12 Uhr' }, diff --git a/test/shopBestellAktionen.test.js b/test/shopBestellAktionen.test.js index fb062cb..006d981 100644 --- a/test/shopBestellAktionen.test.js +++ b/test/shopBestellAktionen.test.js @@ -58,6 +58,8 @@ const cookie = customerCookie('c1') const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, abholung: { aktiv: true, adresse: 'Hauptstraße 5, 67655 Kaiserslautern', hinweis: 'Mo–Fr 8–12 Uhr' }, diff --git a/test/shopBestellungen.test.js b/test/shopBestellungen.test.js index be674c9..6e76192 100644 --- a/test/shopBestellungen.test.js +++ b/test/shopBestellungen.test.js @@ -44,6 +44,8 @@ const PORTAL = 'https://project.webklar.com' const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, abholung: { aktiv: true, adresse: 'Hauptstraße 5, 67655 Kaiserslautern', hinweis: 'Mo–Fr 8–12 Uhr' }, diff --git a/test/shopEinstellungen.test.js b/test/shopEinstellungen.test.js index 0028a28..3e6808d 100644 --- a/test/shopEinstellungen.test.js +++ b/test/shopEinstellungen.test.js @@ -51,10 +51,10 @@ test('Standardwerte sind eingefroren, so wie im Plan und selbst gueltig', () => assert.deepEqual(STANDARD_EINSTELLUNGEN, { shopname: '', anbieter: { name: '', strasse: '', plz: '', ort: '', land: 'DE', email: '', telefon: '', ustId: '' }, - kleinunternehmer: true, + kleinunternehmer: null, versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null }, abholung: { aktiv: false, adresse: '', hinweis: '' }, - lieferzeit: '2–4 Werktage', + lieferzeit: '', zahlungsarten: ['card'], akzent: '#111111', mindestbestellCent: null, @@ -174,6 +174,13 @@ test('Akzent, Mindestbestellwert, Ruecksendekosten, Kleinunternehmer', () => { assert.ok(fehler(komplett({ ruecksendekosten: 'egal' }))?.ruecksendekosten) assert.equal(ok(komplett({ kleinunternehmer: false })).kleinunternehmer, false) assert.ok(fehler(komplett({ kleinunternehmer: 'nein' }))?.kleinunternehmer) + // I-4: keine Vorbelegung - null (oder fehlend) bleibt null, zaehlt aber nicht als vollstaendig + assert.equal(ok(komplett({ kleinunternehmer: null })).kleinunternehmer, null) + assert.equal(ok({ ...komplett(), kleinunternehmer: undefined }).kleinunternehmer, null) + assert.equal(istVollstaendig(komplett({ kleinunternehmer: null })), false) + assert.equal(istVollstaendig(komplett({ kleinunternehmer: true })), true) + assert.equal(istVollstaendig(komplett({ kleinunternehmer: false })), true) + assert.equal(istVollstaendig(komplett({ lieferzeit: undefined })), false, 'Lieferzeit ohne Vorbelegung') }) test('mehrere Fehler zugleich, alle Texte in Du-Form ohne Sie', () => { @@ -206,7 +213,8 @@ test('ladeEinstellungen: kaputt oder fehlend ergibt Standard, Teilwerte werden a assert.equal(teil.anbieter.land, 'DE') assert.equal(teil.versand.freiAbCent, 5000) assert.deepEqual(teil.versand.kostenCent, { DE: 490 }) - assert.equal(teil.lieferzeit, '2–4 Werktage') + assert.equal(teil.lieferzeit, '') + assert.equal(teil.kleinunternehmer, null, 'Steuerart ist nicht vorbelegt') // gespeicherte Werte werden nicht in die Standardwerte geschrieben teil.versand.laender.push('AT') assert.deepEqual(STANDARD_EINSTELLUNGEN.versand.laender, ['DE']) diff --git a/test/shopKontext.test.js b/test/shopKontext.test.js index 1203b18..18eb772 100644 --- a/test/shopKontext.test.js +++ b/test/shopKontext.test.js @@ -33,6 +33,8 @@ const nach = (s) => new Date(T0.getTime() + s * 1000) const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, } @@ -278,6 +280,20 @@ test('bereitschaft: einstellungen', async () => { assert.equal(punkt(await K.bereitschaft(k), 'einstellungen').ok, false) }) +test('bereitschaft I-4: Steuerart nicht gewählt (null) oder Lieferzeit leer - Punkt einstellungen offen, Shop bleibt zu', async () => { + for (const luecke of [{ kleinunternehmer: null }, { lieferzeit: '' }]) { + await aufbau({ shop: { einstellungen: JSON.stringify({ ...EINSTELLUNGEN, ...luecke }) } }) + const c = await K.ladeShopFuerProjekt(KUNDE, PROJEKT) + const r = await K.bereitschaft(c) + assert.equal(punkt(r, 'einstellungen').ok, false, JSON.stringify(luecke)) + assert.match(punkt(r, 'einstellungen').text, /Steuerart/) + assert.equal(r.bereit, false) + assert.equal(await K.istGeoeffnet(c), false) + } + await aufbau({ shop: { einstellungen: JSON.stringify({ ...EINSTELLUNGEN, kleinunternehmer: false }) } }) + assert.equal(punkt(await K.bereitschaft(await K.ladeShopFuerProjekt(KUNDE, PROJEKT)), 'einstellungen').ok, true) +}) + test('bereitschaft und Oeffnung: Rechtstexte offen, geaendert, bestaetigt', async () => { // nie bestaetigt await aufbau({ shop: { rechtstexte: JSON.stringify({ entwurf: TEXTE, bestaetigt: null }) } }) diff --git a/test/shopMails.test.js b/test/shopMails.test.js index d438b60..6241de8 100644 --- a/test/shopMails.test.js +++ b/test/shopMails.test.js @@ -33,6 +33,8 @@ const BASIS_VORHER = config.publicBaseUrl const einstellungen = (over = {}) => ({ ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Töpferei Muster', anbieter: { name: 'Erika Muster', strasse: 'Hauptstr. 1', plz: '67722', ort: 'Winnweiler', land: 'DE', email: 'info@toepferei.example', telefon: '', ustId: '' }, lieferzeit: '2–4 Werktage', diff --git a/test/shopPreise.test.js b/test/shopPreise.test.js index c4a5ad8..27e6cf7 100644 --- a/test/shopPreise.test.js +++ b/test/shopPreise.test.js @@ -120,6 +120,13 @@ test('Kleinunternehmer: keine Steuer, Hinweis nach Paragraph 19', () => { 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 }) diff --git a/test/shopProdukte.test.js b/test/shopProdukte.test.js index f07f189..15c09f4 100644 --- a/test/shopProdukte.test.js +++ b/test/shopProdukte.test.js @@ -80,6 +80,7 @@ test('Steuersatz: 0, 7, 19; Kleinunternehmer erzwingt 0', () => { assert.equal(ok(basis({ steuersatz: 19 }), klein).steuersatz, 0) assert.equal(ok(basis({ steuersatz: 7 }), klein).steuersatz, 0) assert.equal(ok(basis(), klein).steuersatz, 0) + assert.equal(ok(basis({ steuersatz: 19 }), { kleinunternehmer: null }).steuersatz, 19, 'Steuerart nicht gewählt: kein Kleinunternehmer') }) test('Bestand: ganze Zahl ab 0 oder unbegrenzt', () => { diff --git a/test/shopRechtstexte.test.js b/test/shopRechtstexte.test.js index 8096428..d25ada3 100644 --- a/test/shopRechtstexte.test.js +++ b/test/shopRechtstexte.test.js @@ -19,6 +19,8 @@ const NB = '\u00a0' const basis = () => { const e = JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)) e.anbieter = { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '0631 123456', ustId: 'DE123456789' } + e.lieferzeit = '2–4 Werktage' + e.kleinunternehmer = false return e } const WIDERRUF_URL = 'https://brot.example/shop#widerruf' @@ -157,6 +159,15 @@ test('AGB § 2 I-1: nur SEPA - keine Sofort-Bestätigung, nur Eingangsmail und V assert.ok(t.includes('Bei SEPA-Lastschrift senden wir Ihnen zunächst eine E-Mail über den Eingang Ihrer Bestellung;')) }) +test('I-4: Steuerart noch nicht gewählt (null) rechnet in den Mustern wie Regelbesteuerung, Lieferzeit leer ergibt Platzhalter', () => { + const e = basis() + e.kleinunternehmer = null + e.lieferzeit = '' + assert.ok(musterAgb(e).includes('Alle Preise enthalten die gesetzliche Umsatzsteuer.')) + assert.ok(musterAgb(e).includes('Die Lieferzeit beträgt [Lieferzeit]')) + assert.ok(versandUndZahlung(e).includes('Alle Preise inkl. gesetzlicher USt.')) +}) + test('versandUndZahlung: Liefergebiet, Kosten je Land, frei ab, Abholung, Zahlungsarten, Mindestbestellwert', () => { const e = basis() e.versand = { aktiv: true, laender: ['DE', 'AT'], kostenCent: { DE: 490, AT: 990 }, freiAbCent: 5000 } diff --git a/test/shopRoutes.test.js b/test/shopRoutes.test.js index 888b441..24f421e 100644 --- a/test/shopRoutes.test.js +++ b/test/shopRoutes.test.js @@ -48,6 +48,8 @@ const cookie = customerCookie('c1') const KV = { cookie: kundenansichtCookie({ customerId: 'c1' }), headers: { 'X-Kundenansicht': 'a'.repeat(32) } } const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Töpferei Keramik', anbieter: { name: 'Töpferei Keramik', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@toepferei.example', telefon: '', ustId: '' }, } @@ -146,7 +148,8 @@ test('Zustaende: ordered und off => 409; disabled nur lesend; setup legt shopCon assert.equal(aw.docs('shopConfig').size, 0) const setup = await lesen('einstellungen') assert.equal(setup.status, 200) - assert.equal(setup.body.einstellungen.lieferzeit, '2–4 Werktage') + assert.equal(setup.body.einstellungen.lieferzeit, '') + assert.equal(setup.body.einstellungen.kleinunternehmer, null) assert.equal(setup.body.vollstaendig, false) const angelegt = shopConfigDoc(feature) assert.deepEqual([angelegt.customerId, angelegt.projectId, angelegt.siteKey], ['c1', 'pt1', feature.siteKey]) diff --git a/test/shopTabEinstellungen.test.js b/test/shopTabEinstellungen.test.js index 1f62b54..4156e53 100644 --- a/test/shopTabEinstellungen.test.js +++ b/test/shopTabEinstellungen.test.js @@ -11,10 +11,10 @@ import { ANBIETER_HINWEIS, PAYPAL_HINWEIS, SEPA_WARNUNG, STRIPE_HINWEIS, render const STANDARD = { shopname: '', anbieter: { name: '', strasse: '', plz: '', ort: '', land: 'DE', email: '', telefon: '', ustId: '' }, - kleinunternehmer: true, + kleinunternehmer: null, versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null }, abholung: { aktiv: false, adresse: '', hinweis: '' }, - lieferzeit: '2–4 Werktage', + lieferzeit: '', zahlungsarten: ['card'], akzent: '#111111', mindestbestellCent: null, @@ -80,6 +80,8 @@ async function oeffne(fx, ctxExtra) { } const feldVon = (root, name) => root.querySelector(`[data-feld="${name}"]`) +// Steuerart: zwei Radiobuttons ohne Vorauswahl +const steuerRadio = (root, wert) => root.querySelector(`[data-feld="kleinunternehmer"][value="${wert}"]`) const fehlerVon = (root, name) => root.querySelector(`[data-fehler="${name}"]`) const speichernKnopf = (root) => root.querySelector('[data-aktion="einstellungen-speichern"]') const puts = (fx) => fx.fetchLog.filter((a) => a.method === 'PUT') @@ -95,13 +97,18 @@ test('Standardwerte: Felder gefüllt, Länder nur DE, Karte fest, Hinweise zu Kl assert.equal(feldVon(root, 'akzent').type, 'color') assert.equal(feldVon(root, 'akzent').value, '#111111') assert.equal(feldVon(root, 'anbieter.land').value, 'DE') - assert.equal(feldVon(root, 'kleinunternehmer').checked, true) + // I-4: keine Vorauswahl bei der Steuerart, Lieferzeit leer + assert.equal(steuerRadio(root, 'ja').type, 'radio') + assert.equal(steuerRadio(root, 'nein').type, 'radio') + assert.equal(steuerRadio(root, 'ja').checked, false) + assert.equal(steuerRadio(root, 'nein').checked, false) + assert.equal(steuerRadio(root, 'ja').getAttribute('name'), steuerRadio(root, 'nein').getAttribute('name')) assert.equal(feldVon(root, 'versand.aktiv').checked, true) assert.equal(feldVon(root, 'versand.laender').value, 'DE') assert.equal(feldVon(root, 'versand.kostenCent.DE').value, '4,90') assert.equal(feldVon(root, 'versand.kostenCent.DE').type, 'text', 'Betrag als Text, Komma bleibt erlaubt') assert.equal(feldVon(root, 'versand.freiAbCent').value, '') - assert.equal(feldVon(root, 'lieferzeit').value, '2–4 Werktage') + assert.equal(feldVon(root, 'lieferzeit').value, '') assert.equal(feldVon(root, 'mindestbestellCent').value, '') assert.equal(feldVon(root, 'ruecksendekosten').value, 'kaeufer') assert.equal(fx.text(root).includes(ANBIETER_HINWEIS), true) @@ -109,7 +116,8 @@ test('Standardwerte: Felder gefüllt, Länder nur DE, Karte fest, Hinweise zu Kl assert.equal(fx.text(root).includes(STRIPE_HINWEIS), true) assert.equal(fx.text(root).includes(PAYPAL_HINWEIS), true) assert.match(SEPA_WARNUNG, /^Bei Lastschrift ist das Geld erst nach einigen Tagen sicher\. Versende erst, wenn die Bestellung als bezahlt markiert ist\.$/) - assert.match(fx.text(root), /Kleinunternehmer nach § 19 UStG/) + assert.match(fx.text(root), /Ich bin Kleinunternehmer nach § 19 UStG/) + assert.match(fx.text(root), /Ich weise Umsatzsteuer aus/) // Österreich und Abholung sind zu: ihre Felder sind versteckt assert.equal(feldVon(root, 'versand.kostenCent.AT').closest('.sh-versand-at').hidden, true) assert.equal(feldVon(root, 'abholung.adresse').closest('.sh-abhol-details').hidden, true) @@ -134,7 +142,8 @@ test('gespeicherte Werte werden angezeigt, Österreich und Abholung sind sichtba assert.equal(feldVon(root, 'akzent').value, '#ff8800') assert.equal(feldVon(root, 'anbieter.plz').value, '67655') assert.equal(feldVon(root, 'anbieter.ustId').value, 'DE123456789') - assert.equal(feldVon(root, 'kleinunternehmer').checked, false) + assert.equal(steuerRadio(root, 'nein').checked, true) + assert.equal(steuerRadio(root, 'ja').checked, false) assert.equal(feldVon(root, 'versand.laender').value, 'DE,AT') assert.equal(feldVon(root, 'versand.kostenCent.AT').value, '9,90') assert.equal(feldVon(root, 'versand.kostenCent.AT').closest('.sh-versand-at').hidden, false) @@ -164,6 +173,21 @@ test('Schalter blenden Österreich, Versand- und Abholfelder ein und aus', mitDo })) /* ------------------------------------------------------------- Speichern --- */ +test('Steuerart I-4: Radiobuttons schließen sich aus, ohne Wahl wird null gespeichert, mit Wahl true oder false', mitDom({ get: ANTWORT(STANDARD, 1) }, async (fx) => { + const { root } = await oeffne(fx) + await fx.klick(speichernKnopf(root)) + assert.equal(puts(fx)[0].body.einstellungen.kleinunternehmer, null) + await fx.klick(steuerRadio(root, 'ja')) + assert.equal(steuerRadio(root, 'ja').checked, true) + assert.equal(steuerRadio(root, 'nein').checked, false) + await fx.klick(speichernKnopf(root)) + assert.equal(puts(fx)[1].body.einstellungen.kleinunternehmer, true) + await fx.klick(steuerRadio(root, 'nein')) + assert.equal(steuerRadio(root, 'ja').checked, false) + await fx.klick(speichernKnopf(root)) + assert.equal(puts(fx)[2].body.einstellungen.kleinunternehmer, false) +})) + test('Speichern: Körper mit ganzen Cent, Version, Meldung und neuLaden; der Stand des Servers kommt zurück ins Formular', mitDom({ get: ANTWORT(STANDARD, 2), put: (aufruf) => ({ body: ANTWORT({ ...aufruf.body.einstellungen, anbieter: { ...aufruf.body.einstellungen.anbieter, email: 'erika@example.com' } }, 3) }), @@ -177,7 +201,7 @@ test('Speichern: Körper mit ganzen Cent, Version, Meldung und neuLaden; der Sta await fx.setze(feldVon(root, 'anbieter.ort'), 'Kaiserslautern') await fx.setze(feldVon(root, 'anbieter.email'), 'Erika@Example.com') await fx.setze(feldVon(root, 'anbieter.ustId'), 'DE123456789') - await fx.klick(feldVon(root, 'kleinunternehmer')) + await fx.klick(steuerRadio(root, 'nein')) await fx.setze(feldVon(root, 'versand.laender'), 'DE,AT') await fx.setze(feldVon(root, 'versand.kostenCent.DE'), '4,9') await fx.setze(feldVon(root, 'versand.kostenCent.AT'), '9,90') diff --git a/test/shopWebhook.test.js b/test/shopWebhook.test.js index ae68996..b7b12be 100644 --- a/test/shopWebhook.test.js +++ b/test/shopWebhook.test.js @@ -53,6 +53,8 @@ const PORTAL = 'https://project.webklar.com' const EINSTELLUNGEN = { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Brot & Mehr', anbieter: { name: 'Brot & Mehr GmbH', strasse: 'Hauptstraße 5', plz: '67655', ort: 'Kaiserslautern', land: 'DE', email: 'shop@brot.example', telefon: '', ustId: '' }, abholung: { aktiv: true, adresse: 'Hauptstraße 5, 67655 Kaiserslautern', hinweis: 'Mo–Fr 8–12 Uhr' }, diff --git a/test/shopWiderrufe.test.js b/test/shopWiderrufe.test.js index 1498e31..ed79b6d 100644 --- a/test/shopWiderrufe.test.js +++ b/test/shopWiderrufe.test.js @@ -47,6 +47,8 @@ const ctxMit = (state = 'active', id = 'f_shop01') => ({ geoeffnet: false, einstellungen: { ...JSON.parse(JSON.stringify(STANDARD_EINSTELLUNGEN)), + kleinunternehmer: true, + lieferzeit: '2–4 Werktage', shopname: 'Töpferei Muster', anbieter: { name: 'Erika Muster', strasse: 'Hauptstr. 1', plz: '67722', ort: 'Winnweiler', land: 'DE', email: 'info@toepferei.example', telefon: '', ustId: '' }, },