diff --git a/apps/api/src/modules/catalog/index.ts b/apps/api/src/modules/catalog/index.ts index 42aa2ce..5c31527 100644 --- a/apps/api/src/modules/catalog/index.ts +++ b/apps/api/src/modules/catalog/index.ts @@ -11,7 +11,7 @@ import { one, query, run, tx } from '../../core/db.js'; import { audit } from '../../core/audit.js'; import { clientIp, requireAuth, requirePermission } from '../../core/auth.js'; import { AppError, badRequest, conflict, notFound } from '../../core/errors.js'; -import { canInOrg } from '../../core/policy.js'; +import { can, canInOrg } from '../../core/policy.js'; import type { KcModule } from '../../core/module.js'; const int = (max: number) => z.number().int().min(0).max(max); @@ -272,7 +272,9 @@ export const catalogModule: KcModule = { const q = z.object({ org: z.string().uuid() }).parse(req.query); if (!canInOrg(a.principal, q.org, 'orders.read', 'products.read')) throw notFound(); const org = await one('SELECT customer_type FROM organizations WHERE id = ?', [q.org]); - const rows = await query(`${productSelect} WHERE p.status = 'active' AND p.orderable_by_customer = 1 ORDER BY v.name`); + // Personal darf auch Produkte bestellen, die Kunden nicht selbst bestellen können (siehe POST /orders) + const staff = can(a.principal, 'orders.write'); + const rows = await query(`${productSelect} WHERE p.status = 'active'${staff ? '' : ' AND p.orderable_by_customer = 1'} ORDER BY v.name`); return Promise.all(rows.map(async (r) => { const price = calculatePrice({ basis: r.price_basis, setupCents: r.setup_cents, recurringCents: r.recurring_cents, taxBp: r.tax_bp, interval: r.billing_interval, quantity: 1, discountBp: 0 }); // Staffelpreise nach Laufzeit (optional): je gewählter Laufzeit ein eigener Gesamtpreis statt des Basispreises. @@ -280,7 +282,7 @@ export const catalogModule: KcModule = { termMonths: t.term_months, price: calculatePrice({ basis: r.price_basis, setupCents: 0, recurringCents: t.recurring_cents, taxBp: r.tax_bp, interval: r.billing_interval, quantity: 1, discountBp: 0 }).recurring, })); - return { id: r.id, sku: r.sku, category: r.category, name: r.vname, description: r.description, requiresApproval: !!r.requires_approval, customerType: org?.customer_type, price, termMonths: r.term_months, renewal: r.renewal, renewalTermMonths: r.renewal_term_months, noticeDays: r.notice_days, termPrices: tiers }; + return { id: r.id, sku: r.sku, category: r.category, name: r.vname, description: r.description, requiresApproval: !!r.requires_approval, orderableByCustomer: !!r.orderable_by_customer, customerType: org?.customer_type, price, termMonths: r.term_months, renewal: r.renewal, renewalTermMonths: r.renewal_term_months, noticeDays: r.notice_days, termPrices: tiers }; })); }); }, diff --git a/apps/web/src/app/(app)/bestellen/page.tsx b/apps/web/src/app/(app)/bestellen/page.tsx index 37ef405..275ac56 100644 --- a/apps/web/src/app/(app)/bestellen/page.tsx +++ b/apps/web/src/app/(app)/bestellen/page.tsx @@ -5,7 +5,7 @@ import { api, errMsg } from '@/lib/api'; import { useSession } from '@/lib/session'; import { Alert, Empty, eur, Field, type Price, PriceText } from '@/components/ui'; -interface P { id: string; sku: string; category: string; name: string; description: string | null; requiresApproval: boolean; price: Price; termMonths: number; renewal: string; renewalTermMonths: number; noticeDays: number; termPrices: { termMonths: number; price: { net: number; tax: number; gross: number } }[] } +interface P { id: string; sku: string; category: string; name: string; description: string | null; requiresApproval: boolean; orderableByCustomer?: boolean; price: Price; termMonths: number; renewal: string; renewalTermMonths: number; noticeDays: number; termPrices: { termMonths: number; price: { net: number; tax: number; gross: number } }[] } interface Cust { id: string; name: string; customerNumber: string; customerType: 'private' | 'business' } export default function Bestellen() { const { me, can } = useSession(); const r = useRouter(); const staff = can('orders.write'); @@ -23,7 +23,7 @@ export default function Bestellen() {
Wird geladen …
: cat.length === 0 ?{p.description}
} +Nur durch das Personal bestellbar
}{p.description &&{p.description}
}{p.termMonths > 0 ? `Mindestlaufzeit ${p.termMonths} Monate` : 'Keine Mindestlaufzeit'}{p.renewal === 'auto' ? `, Verlängerung um ${p.renewalTermMonths} Monat(e)` : ''}, Kündigungsfrist {p.noticeDays} Tage