Webshop B2: Team-Entscheidung über die Anfrage, Übersicht /api/team/shop und Kundenmail K24

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:43:38 +00:00
1 parent 608ff61d3d
commit 0fb84021f8
6 files changed
+512 -4

No files matched your search

+3
View File
@@ -8,6 +8,7 @@ import { takeoverAdminRouter } from '../takeoverCredits.js'
import customersRoutes from './customers.js'
import filesRoutes from './files.js'
import previewRoutes from './preview.js'
import shopTeamRoutes from './shop.js'
import termineTeamRoutes from './termine.js'
// Team-Eingang fuer das Ticketsystem: alle Mitarbeiter, Appwrite-JWT (Spec 2026-10-01 Admin-Panel ins Ticketsystem)
@@ -25,5 +26,7 @@ router.use('/storage-tasks', storageTasksAdminRouter)
router.use('/funktionen', funktionenAdminRouter)
// Terminplaner-Betrieb (Status fuer Budget, Warteschlange, Anmeldepause, Tagesjob)
router.use('/termine', termineTeamRoutes)
// Webshop: Stand je Projekt und Entscheidung ueber die Anfrage
router.use('/shop', shopTeamRoutes)
export default router
+92
View File
@@ -0,0 +1,92 @@
import { Router } from 'express'
import { config } from '../../config.js'
import { Query, countDocuments, listDocuments } from '../../services/appwriteAdmin.js'
import { anfrageAnsicht, entscheideAnfrage, leseShopConfig } from '../../services/shop/anfrage.js'
import { listCustomerFeatureDocs } from '../../services/siteFeatures.js'
/**
* Webshop im Ticketsystem (Kunden-Reiter "Funktionen"): Stand je Projekt und Entscheidung ueber die Anfrage.
* Eingehaengt in routes/team/index.js unter /api/team/shop, dort hinter requireStaffJwt.
*/
const router = Router()
const sendError = (res, err) => {
const status = err.status >= 400 && err.status < 600 ? err.status : 500
if (status >= 500) console.error('[team-shop]', err.message)
return res.status(status).json({ error: err.message || 'Interner Serverfehler', ...(err.body?.code ? { code: err.body.code } : {}) })
}
const route = (fn) => (req, res) => fn(req, res).catch((err) => sendError(res, err))
const person = (req) => ({ name: req.session.adminName || req.session.name || '', email: req.session.email || '' })
/** Zaehler oder null, wenn die Abfrage scheitert (die Uebersicht bleibt lesbar). */
async function zaehle(collection, queries, was) {
try {
return await countDocuments(config.collections[collection], queries)
} catch (err) {
console.warn(`[team-shop] ${was} nicht lesbar: ${err.message}`)
return null
}
}
/** stripeStatus (JSON-Text im shopConfig) als Objekt oder null, wenn leer oder nicht lesbar. */
function stripeStatus(text) {
try {
const roh = typeof text === 'string' && text ? JSON.parse(text) : null
return roh !== null && typeof roh === 'object' && !Array.isArray(roh) ? roh : null
} catch {
return null
}
}
async function projektStand(project, feature) {
let shop = null
if (feature) {
try {
shop = await leseShopConfig(feature.$id)
} catch (err) {
console.warn(`[team-shop] shopConfig ${feature.$id} nicht lesbar: ${err.message}`)
}
}
const shopId = feature?.$id
const [artikel, offeneBestellungen] = shopId
? await Promise.all([
zaehle('shopProducts', [Query.equal('shopId', shopId)], 'Artikelzahl'),
zaehle('shopOrders', [Query.equal('shopId', shopId), Query.equal('status', 'bezahlt')], 'Bestellzahl'),
])
: [0, 0]
return {
projectId: project.$id,
projectName: project.projectName || '',
state: feature?.state || 'off',
anfrage: shop ? anfrageAnsicht(shop) : null,
stripe: { accountId: Boolean(shop?.stripeAccountId), status: stripeStatus(shop?.stripeStatus) },
geoeffnet: shop?.geoeffnet === true,
zahlen: { artikel, offeneBestellungen },
}
}
// Uebersicht je Projekt des Kunden; archivierte Projekte nur, wenn dort ein Shop-Dokument existiert
router.get('/:customerId', route(async (req, res) => {
const customerId = req.params.customerId
const [docs, projects] = await Promise.all([
listCustomerFeatureDocs(customerId),
listDocuments(config.collections.websiteProjects, [Query.equal('customerId', customerId), Query.orderDesc('$createdAt'), Query.limit(1000)]),
])
const shopDocs = new Map(docs.filter((d) => d.featureKey === 'shop').map((d) => [d.projectId, d]))
const sichtbar = projects.filter((p) => !p.archived || shopDocs.has(p.$id))
res.json({ projects: await Promise.all(sichtbar.map((p) => projektStand(p, shopDocs.get(p.$id)))) })
}))
router.post('/:customerId/anfrage', route(async (req, res) => {
const { projectId, entscheidung, begruendung } = req.body || {}
const ergebnis = await entscheideAnfrage({
customerId: req.params.customerId,
projectId,
entscheidung,
begruendung,
person: person(req),
})
res.json({ ok: true, anfrage: ergebnis.anfrage, ...(ergebnis.mailFehler ? { mailFehler: true } : {}) })
}))
export default router