feat(orders): reopen cancelled orders only after confirmation; accurate sales
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m24s

Cancelled orders:
- Moving an order out of STORNIERT is possible again, but only after a
  warning is confirmed in the status form (OrderStatusForm); without
  the confirmation the server changes nothing.
- Reopening books the counter-entry to the cancellation: goods that the
  cancellation restocked are deducted again (atomically, only if enough
  stock is left, otherwise the order stays cancelled). A later second
  cancellation therefore restocks +1 again instead of leaving +2.
- The warning states that no new payment is collected.

Dashboard figures:
- One definition of a counted sale (lib/sales.ts): not unpaid (NEU),
  not cancelled, no refunded return. Used for revenue today/month, sold
  glasses, average cart, top products/categories, revenue trend and
  repeat customers.
- "Verkaufte Gläser" previously summed every order item, including
  cancelled orders and refunded returns.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-27 01:14:12 +02:00
1 parent dc1523aafd
commit e0b47faf45
11 files changed
+243 -82

No files matched your search

+8 -7
View File
@@ -1,6 +1,7 @@
import Link from "next/link";
import { ApiCoreSyncPanel, ApiCoreSyncResult } from "@/components/apicore-sync-panel";
import { prisma } from "@/lib/prisma";
import { COUNTED_SALE_WHERE } from "@/lib/sales";
import { formatPrice } from "@/lib/format";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
@@ -119,21 +120,21 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
] = await Promise.all([
prisma.order.aggregate({
_sum: { totalCents: true },
where: { createdAt: { gte: startOfDay() }, status: { not: "STORNIERT" } },
where: { createdAt: { gte: startOfDay() }, ...COUNTED_SALE_WHERE },
}),
prisma.order.aggregate({
_sum: { totalCents: true },
where: { createdAt: { gte: startOfMonth() }, status: { not: "STORNIERT" } },
where: { createdAt: { gte: startOfMonth() }, ...COUNTED_SALE_WHERE },
}),
prisma.order.count({
where: { status: { notIn: ["VERSENDET", "ABGEHOLT", "STORNIERT"] } },
}),
prisma.inventory.aggregate({ _sum: { quantity: true } }),
prisma.orderItem.aggregate({ _sum: { quantity: true } }),
prisma.orderItem.aggregate({ _sum: { quantity: true }, where: { order: COUNTED_SALE_WHERE } }),
prisma.order.aggregate({
_avg: { totalCents: true },
_count: true,
where: { status: { not: "STORNIERT" } },
where: COUNTED_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,
@@ -150,11 +151,11 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
// unten); ueber eine Relation laesst sich nicht gruppieren.
prisma.orderItem.groupBy({
by: ["productId", "productName"],
where: { order: { status: { not: "STORNIERT" } } },
where: { order: COUNTED_SALE_WHERE },
_sum: { quantity: true, totalCents: true },
}),
prisma.order.findMany({
where: { createdAt: { gte: daysAgo(REVENUE_TREND_DAYS - 1) }, status: { not: "STORNIERT" } },
where: { createdAt: { gte: daysAgo(REVENUE_TREND_DAYS - 1) }, ...COUNTED_SALE_WHERE },
select: { createdAt: true, totalCents: true },
}),
// Stammkunden: Kund:innen mit mehr als einer nicht-stornierten Bestellung.
@@ -164,7 +165,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 }, status: { not: "STORNIERT" } },
where: { customerId: { not: null }, ...COUNTED_SALE_WHERE },
having: { customerId: { _count: { gt: 1 } } },
}),
prisma.discountCode.findMany({