From 2b372be22815a1ec406ef3b21fdde1ef908064b4 Mon Sep 17 00:00:00 2001 From: DanielS Date: Mon, 20 Jul 2026 23:00:02 +0200 Subject: [PATCH] feat(orders): support active status and update RLS --- .agents/skills/lucide-icons-guide/skill.md | 7 ++ shop/lib/actions/orders.ts | 70 ++++++++++++++++--- ...00_update_orders_status_and_rls_helper.sql | 19 +++++ 3 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 .agents/skills/lucide-icons-guide/skill.md create mode 100644 shop/supabase/migrations/20260720000000_update_orders_status_and_rls_helper.sql diff --git a/.agents/skills/lucide-icons-guide/skill.md b/.agents/skills/lucide-icons-guide/skill.md new file mode 100644 index 0000000..3f56e12 --- /dev/null +++ b/.agents/skills/lucide-icons-guide/skill.md @@ -0,0 +1,7 @@ +skill: lucide-icons-guide +description: Integration, Auswahl und Optimierung von Lucide React Icons (lucide.dev) für Next.js 15 (RSC/Server Actions) und Tailwind CSS. +rules: + - Importiere Icons direkt aus `lucide-react` (z. B. `import { Package, ShieldCheck, ShoppingCart } from 'lucide-react'`). + - Achte bei React Server Components (RSC) darauf, dass Icons performant gerendert und keine unnötigen Client-Side Bundles erzeugt werden. + - Verwende konsistente Größen (`size={18}` oder Tailwind `w-5 h-5`) und Stile (z. B. `strokeWidth={1.75}`) passend zum B2B-Design. + - Nutze dynamische Icon-Name-Renderings nur über eine typsichere Map/Registry, um Unused-Code-Elimination (Tree Shaking) zu gewährleisten. \ No newline at end of file diff --git a/shop/lib/actions/orders.ts b/shop/lib/actions/orders.ts index 25aff1e..74f7ab8 100644 --- a/shop/lib/actions/orders.ts +++ b/shop/lib/actions/orders.ts @@ -298,14 +298,15 @@ export async function submitOrder(params: { export async function updateOrderStatus( orderId: string, - newStatus: 'pending' | 'active' | 'completed' | 'cancelled' | 'rejected' + newStatus: 'pending' | 'pending_approval' | 'in_review' | 'approved' | 'active' | 'completed' | 'cancelled' | 'rejected' ) { const admin = createAdminClient() + const supabase = await createClient() - // 1. Fetch current order + // 1. Fetch current order with full details (snapshots) const { data: order, error: fetchError } = await admin .from('orders') - .select('id, user_id, order_number, status') + .select('*') .eq('id', orderId) .single() @@ -316,7 +317,7 @@ export async function updateOrderStatus( const oldStatus = order.status if (oldStatus === newStatus) { - return order + return order as Order } // 2. Update order status @@ -331,7 +332,41 @@ export async function updateOrderStatus( throw new Error(`Fehler beim Aktualisieren des Status: ${updateError?.message || 'Unbekannt'}`) } - // 3. Send email to the retailer (händler) + // 3. Wenn Status auf 'active' wechselt, generiere PDF und lade es hoch (pdf-invoice-generator) + let attachmentBuffer: Buffer | null = null + if (newStatus === 'active') { + try { + const customerSnapshot = updatedOrder.customer_data + const orderSnapshot = updatedOrder.order_data + + const buffer = await renderToBuffer( + React.createElement(InvoicePDF, { + order: updatedOrder, + customer: customerSnapshot, + orderSnapshot, + }) + ) + attachmentBuffer = buffer + + // PDF in Supabase Storage hochladen + const fileName = `ab_${updatedOrder.id}.pdf` + const { error: uploadError } = await admin + .storage + .from('invoices') + .upload(fileName, buffer, { contentType: 'application/pdf', upsert: true }) + + if (uploadError) { + console.error('PDF Upload Error:', uploadError) + } else { + await admin.from('orders').update({ pdf_url: fileName }).eq('id', updatedOrder.id) + updatedOrder.pdf_url = fileName + } + } catch (pdfError) { + console.error('PDF Generation Error on active status transition:', pdfError) + } + } + + // 4. E-Mail mit SMTP an den Besteller senden (b2b-mail-dispatcher) if (order.user_id) { try { const { data: { user }, error: userError } = await admin.auth.admin.getUserById(order.user_id) @@ -340,7 +375,10 @@ export async function updateOrderStatus( const orderNumber = order.order_number || order.id.slice(0, 8) const statusLabelMap: Record = { pending: 'Eingegangen', - active: 'In Bearbeitung', + pending_approval: 'Wartet auf Freigabe', + in_review: 'In Prüfung', + approved: 'Freigegeben', + active: 'Aktiviert', completed: 'Abgeschlossen', cancelled: 'Storniert', rejected: 'Abgelehnt', @@ -350,12 +388,25 @@ export async function updateOrderStatus( const statusEmail = getStatusEmailTemplate(orderNumber, oldLabel, newLabel) - await sendMail({ + const mailOptions: any = { to: user.email, subject: `Statusänderung Ihrer Anfrage ${orderNumber}`, text: statusEmail.text, - html: statusEmail.html - }) + html: statusEmail.html, + } + + // Bei Aktivierung hänge die PDF an + if (newStatus === 'active' && attachmentBuffer) { + mailOptions.attachments = [ + { + filename: `Anfragebestaetigung_${orderNumber}.pdf`, + content: attachmentBuffer, + contentType: 'application/pdf', + } + ] + } + + await sendMail(mailOptions) } } catch (mailError) { console.error('Failed to send status update email:', mailError) @@ -367,6 +418,7 @@ export async function updateOrderStatus( return updatedOrder as Order } + export async function updateOrder( orderId: string, params: { diff --git a/shop/supabase/migrations/20260720000000_update_orders_status_and_rls_helper.sql b/shop/supabase/migrations/20260720000000_update_orders_status_and_rls_helper.sql new file mode 100644 index 0000000..6d36145 --- /dev/null +++ b/shop/supabase/migrations/20260720000000_update_orders_status_and_rls_helper.sql @@ -0,0 +1,19 @@ +-- 1. Order Status Constraint anpassen +ALTER TABLE public.orders DROP CONSTRAINT IF EXISTS orders_status_check; +ALTER TABLE public.orders ADD CONSTRAINT orders_status_check +CHECK (status IN ('pending', 'pending_approval', 'in_review', 'approved', 'active', 'completed', 'cancelled', 'rejected')); + +-- 2. Helper-Funktion für RLS anlegen +CREATE OR REPLACE FUNCTION public.get_auth_company_id() +RETURNS UUID AS $$ + SELECT company_id FROM public.users WHERE id = auth.uid() LIMIT 1; +$$ LANGUAGE sql STABLE SECURITY DEFINER; + +-- 3. RLS-Policies aktualisieren +DROP POLICY IF EXISTS "Partner sehen nur eigene Firmenbestellungen" ON public.orders; +CREATE POLICY "Partner sehen nur eigene Firmenbestellungen" ON public.orders +FOR SELECT USING (company_id = public.get_auth_company_id() OR public.is_admin(auth.uid())); + +DROP POLICY IF EXISTS "Partner sehen nur eigene Endkunden" ON public.end_customers; +CREATE POLICY "Partner sehen nur eigene Endkunden" ON public.end_customers +FOR SELECT USING (partner_id = public.get_auth_company_id() OR public.is_admin(auth.uid()));