feat(orders): support active status and update RLS
Some checks failed
Staging Build / build (push) Has been cancelled
Some checks failed
Staging Build / build (push) Has been cancelled
This commit is contained in:
7
.agents/skills/lucide-icons-guide/skill.md
Normal file
7
.agents/skills/lucide-icons-guide/skill.md
Normal file
@@ -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.
|
||||||
@@ -298,14 +298,15 @@ export async function submitOrder(params: {
|
|||||||
|
|
||||||
export async function updateOrderStatus(
|
export async function updateOrderStatus(
|
||||||
orderId: string,
|
orderId: string,
|
||||||
newStatus: 'pending' | 'active' | 'completed' | 'cancelled' | 'rejected'
|
newStatus: 'pending' | 'pending_approval' | 'in_review' | 'approved' | 'active' | 'completed' | 'cancelled' | 'rejected'
|
||||||
) {
|
) {
|
||||||
const admin = createAdminClient()
|
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
|
const { data: order, error: fetchError } = await admin
|
||||||
.from('orders')
|
.from('orders')
|
||||||
.select('id, user_id, order_number, status')
|
.select('*')
|
||||||
.eq('id', orderId)
|
.eq('id', orderId)
|
||||||
.single()
|
.single()
|
||||||
|
|
||||||
@@ -316,7 +317,7 @@ export async function updateOrderStatus(
|
|||||||
const oldStatus = order.status
|
const oldStatus = order.status
|
||||||
|
|
||||||
if (oldStatus === newStatus) {
|
if (oldStatus === newStatus) {
|
||||||
return order
|
return order as Order
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Update order status
|
// 2. Update order status
|
||||||
@@ -331,7 +332,41 @@ export async function updateOrderStatus(
|
|||||||
throw new Error(`Fehler beim Aktualisieren des Status: ${updateError?.message || 'Unbekannt'}`)
|
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) {
|
if (order.user_id) {
|
||||||
try {
|
try {
|
||||||
const { data: { user }, error: userError } = await admin.auth.admin.getUserById(order.user_id)
|
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 orderNumber = order.order_number || order.id.slice(0, 8)
|
||||||
const statusLabelMap: Record<string, string> = {
|
const statusLabelMap: Record<string, string> = {
|
||||||
pending: 'Eingegangen',
|
pending: 'Eingegangen',
|
||||||
active: 'In Bearbeitung',
|
pending_approval: 'Wartet auf Freigabe',
|
||||||
|
in_review: 'In Prüfung',
|
||||||
|
approved: 'Freigegeben',
|
||||||
|
active: 'Aktiviert',
|
||||||
completed: 'Abgeschlossen',
|
completed: 'Abgeschlossen',
|
||||||
cancelled: 'Storniert',
|
cancelled: 'Storniert',
|
||||||
rejected: 'Abgelehnt',
|
rejected: 'Abgelehnt',
|
||||||
@@ -350,12 +388,25 @@ export async function updateOrderStatus(
|
|||||||
|
|
||||||
const statusEmail = getStatusEmailTemplate(orderNumber, oldLabel, newLabel)
|
const statusEmail = getStatusEmailTemplate(orderNumber, oldLabel, newLabel)
|
||||||
|
|
||||||
await sendMail({
|
const mailOptions: any = {
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject: `Statusänderung Ihrer Anfrage ${orderNumber}`,
|
subject: `Statusänderung Ihrer Anfrage ${orderNumber}`,
|
||||||
text: statusEmail.text,
|
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) {
|
} catch (mailError) {
|
||||||
console.error('Failed to send status update email:', mailError)
|
console.error('Failed to send status update email:', mailError)
|
||||||
@@ -367,6 +418,7 @@ export async function updateOrderStatus(
|
|||||||
return updatedOrder as Order
|
return updatedOrder as Order
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function updateOrder(
|
export async function updateOrder(
|
||||||
orderId: string,
|
orderId: string,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -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()));
|
||||||
Reference in New Issue
Block a user