fix: Befunde aus Sicherheits- und Korrektheitsreview der Bienenpatenschaft
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m57s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m7s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / test (push) Canceled after 1m22s

- Oeffentliche Kuendigung: exakte Identifikation, Limits je Vertrag und
  Adresse, Bestaetigung nur an die Kontoadresse mit Ruecknahme-Link (HMAC)
- Vorschau: Body-Grenze beim Lesen, Parallelitaets- und Minutenlimit, Cache
- Rabatte gelten nicht fuer Patenschaftspositionen
- Abo aktiv erst nach bestaetigter Zahlung, offene Abschluesse zaehlen
  zum Kontingent und werden nach 24 h aufgeraeumt, Sitzung 58 Minuten
- Konto-Loeschung nur ohne laufendes Abo, Urkunden-Dateien werden entfernt
- Abo-Lieferungen mit Zahlungszeile (DHL-Marke moeglich)
- Shop zeigt freien Bestand nach Abo-Reservierung
- Storno/volle Erstattung widerruft die Patenschaft (Status WIDERRUFEN)
- Umsatzsteuer der Patenschaft per VAT_RATE_PERCENT_PATENSCHAFT (19 %)
- Digitale Bestellungen archivieren sich, Kalendertage in Europe/Berlin,
  Monatsenden begrenzt, Datenauskunft um Patenschaften ergaenzt

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-16 15:09:32 +02:00
1 parent f227aeb8f9
commit 515fca1c16
97 files changed
+2979 -254

No files matched your search

+17 -6
View File
@@ -3,7 +3,7 @@ import { ApiCoreSyncPanel, ApiCoreSyncResult } from "@/components/apicore-sync-p
import { prisma } from "@/lib/prisma";
import { pendingReviewWhere } from "@/lib/review-status";
import { countApiCoreStockIssues } from "@/lib/apicore-stock/dashboard";
import { COUNTED_SALE_WHERE } from "@/lib/sales";
import { COUNTED_SALE_WHERE, SHOP_SALE_WHERE } from "@/lib/sales";
import { formatPrice } from "@/lib/format";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
@@ -130,14 +130,20 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
where: { createdAt: { gte: startOfMonth() }, ...COUNTED_SALE_WHERE },
}),
prisma.order.count({
where: { status: { notIn: ["VERSENDET", "ABGEHOLT", "STORNIERT"] } },
where: { status: { notIn: ["VERSENDET", "ABGEHOLT", "STORNIERT"] }, fulfillmentMethod: { not: "DIGITAL" } },
}),
prisma.inventory.aggregate({ _sum: { quantity: true } }),
prisma.orderItem.aggregate({ _sum: { quantity: true }, where: { order: COUNTED_SALE_WHERE } }),
// Glaeser: Patenschafts-Positionen (Urkunde, Abo-Monatszeilen) sind keine.
prisma.orderItem.aggregate({
_sum: { quantity: true },
where: { order: COUNTED_SALE_WHERE, product: { category: { not: "PATENSCHAFT" } } },
}),
// Warenkorbwert nur aus echten Shop-Bestellungen: Abo-Monatsrechnungen
// und 0-€-Abo-Lieferungen sind keine Warenkoerbe.
prisma.order.aggregate({
_avg: { totalCents: true },
_count: true,
where: COUNTED_SALE_WHERE,
where: SHOP_SALE_WHERE,
}),
// Summiert wird in der DATENBANK, nicht hier. Frueher lud diese Abfrage
// JEDE Bestellposition, die es je gab, samt Produkt-Join in den Speicher,
@@ -168,7 +174,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
// machen — mit der Begruendung, Prisma koenne kein HAVING. Es kann.
prisma.order.groupBy({
by: ["customerId"],
where: { customerId: { not: null }, ...COUNTED_SALE_WHERE },
where: { customerId: { not: null }, ...SHOP_SALE_WHERE },
having: { customerId: { _count: { gt: 1 } } },
}),
prisma.discountCode.findMany({
@@ -270,7 +276,12 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
const [admin, waitingOrders, waitingReturns, waitingReviews, waitingInquiries, sponsorshipItems] = await Promise.all([
getCurrentAdmin(),
prisma.order.count({
where: { status: { in: ["BEZAHLT", "IN_VORBEREITUNG", "VERSANDBEREIT", "ABHOLBEREIT"] }, archivedAt: null },
// DIGITAL (nur Patenschaften, Abo-Monatsrechnungen): nichts zu packen.
where: {
status: { in: ["BEZAHLT", "IN_VORBEREITUNG", "VERSANDBEREIT", "ABHOLBEREIT"] },
archivedAt: null,
fulfillmentMethod: { not: "DIGITAL" },
},
}),
prisma.returnRequest.count({ where: { status: "ANGEFRAGT" } }),
prisma.review.count({ where: pendingReviewWhere() }),