Shop E3: Händler-Aktionen (versendet, abholbereit, abgeschlossen, storniert), Erstattung unter Sperre, Bestellliste, Detail, CSV und Kundenrouten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eb616ddbe8
commit
6d8487633b
3 files changed
+1159
-1
No files matched your search
@@ -9,6 +9,8 @@ import { createWriteLimiter } from '../services/writeLimiter.js'
|
||||
import { anfrageAnsicht, ensureShopConfig, leseShopConfig, stelleAnfrage, zieheZurueck } from '../services/shop/anfrage.js'
|
||||
import { shopFuerKunde } from '../services/shop/schalter.js'
|
||||
import { kontoAnlegen, kontoStatus, onboardingLink, statusAusKonto } from '../services/shop/connect.js'
|
||||
import { bestellungKunde, bestellungLesen, csv as bestellungenCsv, erstatte, listeBestellungen, setzeStatus } from '../services/shop/bestellungen.js'
|
||||
import { listeWiderrufe, markiereBearbeitet } from '../services/shop/widerrufe.js'
|
||||
import { uploadSlot } from './chat.js'
|
||||
import { istVollstaendig, speichereEinstellungen } from '../services/shop/einstellungen.js'
|
||||
import { bereitschaft, istGeoeffnet, ladeShop, leereCache } from '../services/shop/kontext.js'
|
||||
@@ -39,6 +41,7 @@ const router = Router()
|
||||
|
||||
const MAX_ANFRAGE_WRITES_PER_HOUR = 20
|
||||
const MAX_WRITES_PER_HOUR = 300
|
||||
const MAX_CSV_PER_HOUR = 30
|
||||
const PRODUKT_DOC_MAX_JSON = 100000
|
||||
const BILD_DATEI_RE = /^([a-z0-9-]{1,64})-(l|s)\.webp$/
|
||||
const MELDUNG = {
|
||||
@@ -84,11 +87,14 @@ const route = (fn) => async (req, res, next) => {
|
||||
const anfrageLimiter = createWriteLimiter({ max: MAX_ANFRAGE_WRITES_PER_HOUR })
|
||||
const limitAnfrage = anfrageLimiter.middleware((req) => getSessionCustomerId(req), MELDUNG.zuViele)
|
||||
const schreibLimiter = createWriteLimiter({ max: MAX_WRITES_PER_HOUR })
|
||||
const csvLimiter = createWriteLimiter({ max: MAX_CSV_PER_HOUR })
|
||||
const limitCsv = csvLimiter.middleware((req) => getSessionCustomerId(req), MELDUNG.zuViele)
|
||||
const limitWrites = schreibLimiter.middleware((req) => getSessionCustomerId(req), MELDUNG.zuViele)
|
||||
|
||||
export function resetShopRoutesForTests() {
|
||||
anfrageLimiter.reset()
|
||||
schreibLimiter.reset()
|
||||
csvLimiter.reset()
|
||||
setzeBildLimitZurueck()
|
||||
}
|
||||
|
||||
@@ -521,6 +527,70 @@ router.post('/stripe/aktualisieren', requireSession, nurKundeStripe, limitWrites
|
||||
res.json({ stripe: stripeAnsicht({ ...shop, stripeStatus: neu }) })
|
||||
}))
|
||||
|
||||
// ---- Bestellungen und Widerrufe (E3)
|
||||
|
||||
const seiteVon = (wert) => {
|
||||
const nr = Number.parseInt(wert, 10)
|
||||
return Number.isInteger(nr) && nr >= 1 ? nr : 1
|
||||
}
|
||||
const textParam = (wert) => (typeof wert === 'string' ? wert : undefined)
|
||||
/** Status aendern verschickt Mails an Kaeufer und erstattet Geld: nie in der Kundenansicht oder "Ansehen als". */
|
||||
const nurHaendler = nurKunde
|
||||
|
||||
// Liste (Lesen auch in der Kundenansicht und bei deaktiviertem Shop)
|
||||
router.get('/bestellungen', requireSession, route(async (req, res) => {
|
||||
const { shop } = await mitShop(req)
|
||||
res.json(await listeBestellungen(shop.id, { status: textParam(req.query.status), suche: textParam(req.query.suche), seite: seiteVon(req.query.seite) }))
|
||||
}))
|
||||
|
||||
// CSV fuer die Buchhaltung (vor /bestellungen/:id, damit der Pfad eindeutig bleibt)
|
||||
router.get('/bestellungen.csv', requireSession, limitCsv, route(async (req, res) => {
|
||||
const { shop } = await mitShop(req)
|
||||
const erg = await bestellungenCsv(shop.id, { von: textParam(req.query.von), bis: textParam(req.query.bis) })
|
||||
res.set({
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
'Content-Disposition': `attachment; filename="${erg.dateiname}"`,
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
})
|
||||
res.send(erg.csv)
|
||||
}))
|
||||
|
||||
router.get('/bestellungen/:id', requireSession, route(async (req, res) => {
|
||||
const { shop } = await mitShop(req)
|
||||
const order = await bestellungLesen(req.params.id)
|
||||
if (!order || order.shopId !== shop.id) throw stop(404, 'Diese Bestellung gibt es nicht.')
|
||||
res.json({ bestellung: bestellungKunde(order) })
|
||||
}))
|
||||
|
||||
router.post('/bestellungen/:id/aktion', requireSession, nurHaendler, limitWrites, route(async (req, res) => {
|
||||
const { shop, doc, project } = await mitShop(req, { schreiben: true })
|
||||
const aktion = typeof req.body?.aktion === 'string' ? req.body.aktion : ''
|
||||
const { order, mailOk } = await setzeStatus({ doc, shop, project }, req.params.id, aktion, {
|
||||
versanddienst: req.body?.versanddienst,
|
||||
sendungsnummer: req.body?.sendungsnummer,
|
||||
})
|
||||
res.json({ bestellung: bestellungKunde(order), mailOk })
|
||||
}))
|
||||
|
||||
router.post('/bestellungen/:id/erstatten', requireSession, nurHaendler, limitWrites, route(async (req, res) => {
|
||||
const { shop, doc, project } = await mitShop(req, { schreiben: true })
|
||||
const { order, erstattung, voll, mailOk } = await erstatte({ doc, shop, project }, req.params.id, {
|
||||
betragCent: req.body?.betragCent,
|
||||
grund: req.body?.grund,
|
||||
})
|
||||
res.json({ bestellung: bestellungKunde(order), erstattung, voll, mailOk })
|
||||
}))
|
||||
|
||||
router.get('/widerrufe', requireSession, route(async (req, res) => {
|
||||
const { shop } = await mitShop(req)
|
||||
res.json(await listeWiderrufe(shop.id, { seite: seiteVon(req.query.seite) }))
|
||||
}))
|
||||
|
||||
router.post('/widerrufe/:id/erledigt', requireSession, nurHaendler, limitWrites, route(async (req, res) => {
|
||||
const { shop } = await mitShop(req, { schreiben: true })
|
||||
res.json({ widerruf: await markiereBearbeitet(shop.id, req.params.id) })
|
||||
}))
|
||||
|
||||
// ---- Bilder
|
||||
|
||||
// Bild als rohe Bytes (application/octet-stream), hoechstens 8 MB; die Pruefung des Shops laeuft vor dem Einlesen
|
||||
|
||||
Reference in new issue
Block a user