Shop Review A I3: Artikel-Editor ueberschreibt den Bestand nicht mehr (bestandGeladen, Bestand nur bei echter Aenderung schreiben)

Der Editor sendet neben bestand den beim Laden gesehenen Wert (bestandGeladen, je Variantenwert mit id). speichereProdukt behaelt unter der Artikelsperre den aktuell gespeicherten Stand, wenn gesendet gleich geladen ist, und schreibt nur vom Haendler geaenderte Bestaende. bestandGeladen wird nie gespeichert; ohne den Wert (alter Browser) gilt der gesendete Bestand wie bisher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 09:32:31 +00:00
1 parent 9c2897a0b0
commit 55fe1c567f
6 files changed
+215 -10

No files matched your search

+70
View File
@@ -396,3 +396,73 @@ test('parallele Anlage vergibt verschiedene sort-Werte', async () => {
const docs = await Promise.all(Array.from({ length: 5 }, (_, i) => speichereProdukt(shop, null, ok(basis({ name: `P${i}` })))))
assert.deepEqual(docs.map((d) => d.sort).sort(), [0, 1, 2, 3, 4])
})
// ---- Bestand beim Bearbeiten (Review A, I3): nur ein vom Haendler geaenderter Bestand wird geschrieben ------------------
const bestandVon = async (id) => JSON.parse((await holeProdukt('shop-a', id)).produkt)
async function mitGeladen(id, roh, normalisiert = ok(roh)) {
return speichereProdukt(SHOP, id, normalisiert, { geladen: P.leseBestandGeladen(roh) })
}
test('leseBestandGeladen: Zahl, null und leer werden gelesen, Unbrauchbares gilt als nicht gesendet', () => {
assert.deepEqual(P.leseBestandGeladen({ bestandGeladen: 5 }), { hat: true, bestand: 5, werte: new Map() })
assert.deepEqual(P.leseBestandGeladen({ bestandGeladen: null }), { hat: true, bestand: null, werte: new Map() })
assert.equal(P.leseBestandGeladen({ bestandGeladen: '3' }).bestand, 3)
assert.equal(P.leseBestandGeladen({ bestandGeladen: '' }).bestand, null)
assert.equal(P.leseBestandGeladen({ bestandGeladen: 'abc' }).hat, false)
assert.equal(P.leseBestandGeladen({}).hat, false)
assert.equal(P.leseBestandGeladen(null).hat, false)
const w = P.leseBestandGeladen({ varianten: { werte: [{ id: 'aaaaaaaa', bestandGeladen: 2 }, { id: 'bbbbbbbb', bestandGeladen: null }, { id: 'cccccccc' }, { bestandGeladen: 1 }] } })
assert.deepEqual([...w.werte], [['aaaaaaaa', 2], ['bbbbbbbb', null]])
})
test('speichern I3: unveraenderter Bestand ueberschreibt nicht den inzwischen verkauften Stand', async () => {
const a = await speichereProdukt(SHOP, null, ok(basis({ name: 'Kerze', bestand: 5 })))
// Es wurden 3 Stueck verkauft
await speichereProdukt(SHOP, a.$id, { ...(await bestandVon(a.$id)), bestand: 2 })
const g = await mitGeladen(a.$id, basis({ name: 'Kerze neu', bestand: 5, bestandGeladen: 5 }))
assert.equal(JSON.parse(g.produkt).name, 'Kerze neu')
assert.equal((await bestandVon(a.$id)).bestand, 2)
})
test('speichern I3: geaenderter Bestand wird geschrieben (auch auf unbegrenzt und auf 0)', async () => {
const a = await speichereProdukt(SHOP, null, ok(basis({ name: 'Kerze', bestand: 5 })))
await speichereProdukt(SHOP, a.$id, { ...(await bestandVon(a.$id)), bestand: 2 })
await mitGeladen(a.$id, basis({ name: 'Kerze', bestand: 9, bestandGeladen: 5 }))
assert.equal((await bestandVon(a.$id)).bestand, 9)
await mitGeladen(a.$id, basis({ name: 'Kerze', bestand: '', bestandGeladen: 9 }))
assert.equal((await bestandVon(a.$id)).bestand, null)
await mitGeladen(a.$id, basis({ name: 'Kerze', bestand: 0, bestandGeladen: null }))
assert.equal((await bestandVon(a.$id)).bestand, 0)
})
test('speichern I3: ohne bestandGeladen gilt der gesendete Bestand wie bisher', async () => {
const a = await speichereProdukt(SHOP, null, ok(basis({ name: 'Kerze', bestand: 5 })))
await speichereProdukt(SHOP, a.$id, ok(basis({ name: 'Kerze', bestand: 7 })))
assert.equal((await bestandVon(a.$id)).bestand, 7)
})
test('speichern I3: Variantenbestand je Wert - unveraenderte Werte behalten den aktuellen Stand, geaenderte und neue Werte nicht', async () => {
const roh = (w) => basis({ name: 'Shirt', preisCent: undefined, varianten: { name: 'Größe', werte: w } })
const a = await speichereProdukt(SHOP, null, ok(roh([{ id: 'aaaaaaaa', name: 'S', preisCent: 800, bestand: 5 }, { id: 'bbbbbbbb', name: 'M', preisCent: 900, bestand: 5 }, { id: 'cccccccc', name: 'L', preisCent: 900, bestand: null }])))
// S: 2 verkauft, M: 5 verkauft
const stand = await bestandVon(a.$id)
stand.varianten.werte[0].bestand = 3
stand.varianten.werte[1].bestand = 0
await speichereProdukt(SHOP, a.$id, stand)
await mitGeladen(a.$id, roh([
{ id: 'aaaaaaaa', name: 'S', preisCent: 800, bestand: 5, bestandGeladen: 5 }, // unberuehrt
{ id: 'bbbbbbbb', name: 'M', preisCent: 900, bestand: 20, bestandGeladen: 5 }, // Haendler hat aufgestockt
{ id: 'cccccccc', name: 'L', preisCent: 900, bestand: null, bestandGeladen: null }, // unbegrenzt bleibt
{ name: 'XL', preisCent: 900, bestand: 4 }, // neuer Wert
]))
const nachher = (await bestandVon(a.$id)).varianten.werte
assert.deepEqual(nachher.map((w) => [w.name, w.bestand]), [['S', 3], ['M', 20], ['L', null], ['XL', 4]])
})
test('speichern I3: Bestand beim Anlegen bleibt wie gesendet; bestandGeladen landet nie im gespeicherten Artikel', async () => {
const a = await mitGeladen(null, basis({ name: 'Neu', bestand: 4, bestandGeladen: 4 }))
assert.equal((await bestandVon(a.$id)).bestand, 4)
assert.ok(!JSON.stringify(await bestandVon(a.$id)).includes('Geladen'))
})
+23
View File
@@ -241,6 +241,29 @@ test('Einstellungen speichern leert den 30-s-Speicher von shopPerSiteKey', async
// ---------------------------------------------------------------- Artikel
test('Artikel I3: Speichern ohne Bestandsaenderung behaelt den inzwischen verkauften Bestand, geaenderter Bestand wird geschrieben', async () => {
await aktiviere()
await schreiben('PUT', 'einstellungen', { einstellungen: EINSTELLUNGEN })
const neu = await schreiben('POST', 'produkte', { produkt: { ...ARTIKEL, bestand: 5, bestandGeladen: 5 } })
assert.equal(neu.status, 201)
const id = neu.body.produkt.id
assert.equal(neu.body.produkt.bestand, 5)
assert.equal(neu.body.produkt.bestandGeladen, undefined)
// Zwischenzeitlich wurden 3 Stueck verkauft
const doc = aw.docs('shopProducts').get(id)
await updateDocument(config.collections.shopProducts, id, { produkt: JSON.stringify({ ...JSON.parse(doc.produkt), bestand: 2 }) })
const nurText = await schreiben('PUT', `produkte/${id}`, { produkt: { ...ARTIKEL, name: 'Tasse 2', bestand: 5, bestandGeladen: 5 } })
assert.equal(nurText.status, 200)
assert.deepEqual([nurText.body.produkt.name, nurText.body.produkt.bestand], ['Tasse 2', 2])
assert.ok(!aw.docs('shopProducts').get(id).produkt.includes('bestandGeladen'))
const aufgestockt = await schreiben('PUT', `produkte/${id}`, { produkt: { ...ARTIKEL, bestand: 12, bestandGeladen: 5 } })
assert.equal(aufgestockt.body.produkt.bestand, 12)
const ohneMarker = await schreiben('PUT', `produkte/${id}`, { produkt: { ...ARTIKEL, bestand: 7 } })
assert.equal(ohneMarker.body.produkt.bestand, 7, 'alter Browser ohne bestandGeladen: wie bisher')
})
test('Artikel: anlegen 201, lesen, aendern, Fehler 400 je Feld, unbekannte ID 404, Kleinunternehmer-Steuer', async () => {
await aktiviere()
await schreiben('PUT', 'einstellungen', { einstellungen: EINSTELLUNGEN }) // Kleinunternehmer => 0 %
+39 -2
View File
@@ -38,6 +38,14 @@ const artikel = (id, name, extra = {}) => {
return a
}
/** Wie koerper, dazu der beim Laden gesehene Bestand (bestandGeladen, je Variantenwert mit id) - so geht ein bestehender Artikel zum Server. */
const geladenKoerper = (a, extra = {}) => {
const k = koerper(a)
k.bestandGeladen = a.bestand ?? null
if (k.varianten) k.varianten = { ...k.varianten, werte: k.varianten.werte.map((w) => ({ ...w, bestandGeladen: w.bestand ?? null })) }
return { ...k, ...extra }
}
/** Der Artikel so, wie er beim Speichern zum Server geht (ohne id, sort, bildUrls). */
const koerper = (a, extra = {}) => {
const { id, sort, bildUrls, ...rest } = a
@@ -168,7 +176,7 @@ test('Schalter „aktiv“: PUT mit dem ganzen Artikel und umgedrehtem aktiv, da
assert.equal(rest.length, 0)
assert.equal(put.method, 'PUT')
assert.equal(put.url, '/api/shop/produkte/a1')
assert.deepEqual(put.body, { projectId: P, produkt: koerper(DREI()[0], { aktiv: false }) })
assert.deepEqual(put.body, { projectId: P, produkt: geladenKoerper(DREI()[0], { aktiv: false }) })
assert.equal(knopf(fx, zeile(fx, panel, 'a1'), 'aktiv').checked, false)
assert.match(meldungen.at(-1).text, /Mütze.*inaktiv/)
}))
@@ -365,7 +373,7 @@ test('Bearbeiten: Werte-IDs der Varianten bleiben im Körper erhalten', mit({
await fx.klick(knopf(fx, zeile(fx, panel, 'a3'), 'bearbeiten'))
await fx.klick(knopf(fx, fx.finde(panel, 'form'), 'speichern'))
assert.deepEqual(aenderungen(fx)[0].body.produkt.varianten.werte.map((w) => w.id), ['aaaaaaaa', 'bbbbbbbb'])
assert.deepEqual(aenderungen(fx)[0].body.produkt, koerper(DREI()[2]))
assert.deepEqual(aenderungen(fx)[0].body.produkt, geladenKoerper(DREI()[2]))
}))
/* --------------------------------------------------------------------- Bilder --- */
@@ -576,3 +584,32 @@ test('Statik: kein innerHTML, outerHTML, insertAdjacentHTML oder document.write;
}
assert.doesNotMatch(code, /from '\.\/termine/, 'keine Importe aus termine*.js')
})
test('Bearbeiten I3: der Körper trägt den beim Laden gesehenen Bestand (bestandGeladen) neben dem Feldwert, je Variantenwert; neue Werte und neue Artikel nicht', mit({
produkte: DREI(),
sonst: (a) => (a.method === 'PUT' ? { body: { produkt: artikel('a3', 'Handschuhe') } } : null),
}, async ({ fx, panel }) => {
await fx.klick(knopf(fx, zeile(fx, panel, 'a3'), 'bearbeiten'))
const form = fx.finde(panel, 'form')
await fx.setze(feldVon(fx, form, 'varianten.werte.0.bestand'), '9')
await fx.klick(knopf(fx, form, 'wert-neu'))
await fx.setze(feldVon(fx, form, 'varianten.werte.2.name'), 'XL')
await fx.setze(feldVon(fx, form, 'varianten.werte.2.preisCent'), '11')
await fx.setze(feldVon(fx, form, 'varianten.werte.2.bestand'), '3')
await fx.klick(knopf(fx, form, 'speichern'))
const { produkt } = aenderungen(fx)[0].body
assert.equal(produkt.bestandGeladen, null)
assert.deepEqual(produkt.varianten.werte.map((w) => [w.id, w.bestand, w.bestandGeladen]), [['aaaaaaaa', 9, 2], ['bbbbbbbb', null, null], [undefined, 3, undefined]])
}))
test('Bearbeiten I3: Einzelbestand - geändert wird der neue Wert gesendet, geladen bleibt der alte', mit({
produkte: DREI(),
sonst: (a) => (a.method === 'PUT' ? { body: { produkt: artikel('a1', 'Mütze') } } : null),
}, async ({ fx, panel }) => {
await fx.klick(knopf(fx, zeile(fx, panel, 'a1'), 'bearbeiten'))
const form = fx.finde(panel, 'form')
await fx.setze(feldVon(fx, form, 'bestand'), '8')
await fx.klick(knopf(fx, form, 'speichern'))
const { produkt } = aenderungen(fx)[0].body
assert.deepEqual([produkt.bestand, produkt.bestandGeladen], [8, 5])
}))