diff --git a/funktionen.md b/funktionen.md index 6574f6c..e0dd561 100644 --- a/funktionen.md +++ b/funktionen.md @@ -6,7 +6,7 @@ Diese Datei beschreibt die Architektur, das Datenmodell, die Kernprozesse und da ## 1. Projekt-Überblick -Der CASPOS Webshop ist ein B2B-Portal für Vertriebspartner. Partner können hier Software-Pakete und Zusatzmodule (für POS/Kassenlösungen) konfigurieren, Angebote/Anfragen für ihre Endkunden erstellen und Lizenzen verwalten. +Der CASPOS Webshop ist ein B2B-Portal für Vertriebspartner. Partner können hier Software-Pakete und Zusatzmodule (für POS/Kassenlösungen) konfigurieren, Anfragen für ihre Endkunden erstellen und Lizenzen verwalten. --- @@ -128,11 +128,13 @@ RLS ist auf Datenbankebene in Postgres implementiert und erzwingt Datenisolierun - `USING (partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()))` - Partner sehen und modifizieren nur Endkunden, deren `partner_id` mit der `company_id` des angemeldeten Benutzers übereinstimmt. - **Bestellungen (`orders`)**: - - Partner sehen und modifizieren nur Bestellungen von Benutzern, die derselben Company angehören (oder ihre eigenen, falls keine Company zugewiesen ist). + - Partner sehen und modifizieren nur Bestellungen von Benutzern, die derselben Company angehören (sie müssen einer company zugewiesen sein). - Admins haben uneingeschränkten Zugriff. - **Lizenzen (`licenses`)**: - Partner sehen nur Lizenzen von Endkunden, die ihrer Company zugeordnet sind. +- **Erstellung: jede bestellung wird der firma des erstellers zugewiesen. Ein admin ist aber fähig einer bestellung nachträglich eine andere firma zuzuweisen. + --- ## Kommentarbereich diff --git a/shop/app/my-orders/page.tsx b/shop/app/my-orders/page.tsx index 077d54f..6875751 100644 --- a/shop/app/my-orders/page.tsx +++ b/shop/app/my-orders/page.tsx @@ -36,13 +36,7 @@ export default async function MyOrdersPage() { let query = supabase.from('orders').select('*') if (dbUser?.company_id) { - const { data: companyUsers } = await supabase - .from('users') - .select('id') - .eq('company_id', dbUser.company_id) - - const userIds = (companyUsers ?? []).map(u => u.id) - query = query.in('user_id', userIds) + query = query.eq('company_id', dbUser.company_id) } else { query = query.eq('user_id', user.id) } diff --git a/shop/app/order/page.tsx b/shop/app/order/page.tsx index 733d542..1459014 100644 --- a/shop/app/order/page.tsx +++ b/shop/app/order/page.tsx @@ -3,6 +3,7 @@ import { createAdminClient } from '@/lib/supabase/admin' import { getProducts, getCategories } from '@/lib/actions/products' import { getEndCustomers } from '@/lib/actions/end-customers' import { OrderWizard } from '@/components/order-wizard' +import { getCompanies } from '@/lib/actions/companies' import { redirect } from 'next/navigation' import { Suspense } from 'react' @@ -121,5 +122,21 @@ async function OrderDataWrapper({ orderId }: { orderId?: string }) { console.error('Fehler beim Laden des Profils:', e) } - return + // Fetch companies list for admin + let companies: any[] = [] + if (userData?.role === 'admin') { + companies = await getCompanies().catch(() => []) + } + + return ( + + ) } diff --git a/shop/components/order-wizard.tsx b/shop/components/order-wizard.tsx index 1949084..a82334f 100644 --- a/shop/components/order-wizard.tsx +++ b/shop/components/order-wizard.tsx @@ -81,16 +81,23 @@ export function OrderWizard({ initialProfile, initialEndCustomers, initialOrder, + isAdmin = false, + companies = [], }: { products: Product[] categories: Category[] initialProfile: Profile | null initialEndCustomers: EndCustomer[] initialOrder?: Order | null + isAdmin?: boolean + companies?: any[] }) { const router = useRouter() const [step, setStep] = useState(initialOrder ? 3 : 1) const [isSubmitting, setIsSubmitting] = useState(false) + const [selectedCompanyId, setSelectedCompanyId] = useState( + initialOrder?.company_id ?? null + ) // Partner-Profil (Fallback) const [customerData] = useState>(initialProfile || {}) @@ -414,6 +421,7 @@ export function OrderWizard({ endCustomer: selectedEndCustomer, billingInterval: selectedBillingInterval, lastLicenseDate: lastLicenseDate || null, + companyId: selectedCompanyId, }) } else { order = await submitOrder({ @@ -482,6 +490,29 @@ export function OrderWizard({ + {/* Admin Company Selector */} + {isAdmin && ( + + + + Zugeordnetes Unternehmen (Admin-Option) + + setSelectedCompanyId(e.target.value || null)} + className="flex h-9 w-full rounded-md border border-white/10 bg-slate-900 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary text-white" + > + Keine Firma zugewiesen + {companies.map((c: any) => ( + + {c.name} + + ))} + + + )} + {/* Modus-Toggle */} { - const { selections, moduleQuantities, customerProfile, endCustomerId, endCustomer, billingInterval, lastLicenseDate } = params + const { selections, moduleQuantities, customerProfile, endCustomerId, endCustomer, billingInterval, lastLicenseDate, companyId } = params const supabase = await createClient() const admin = createAdminClient() @@ -612,15 +614,21 @@ export async function updateOrder( const orderHash = hashOrderSnapshot(orderSnapshot) // 2. Bestellung in DB aktualisieren + const updatePayload: any = { + end_customer_id: endCustomerId ?? null, + total_price: orderSnapshot.total, + customer_data: customerSnapshot, + order_data: orderSnapshot, + order_hash: orderHash, + } + + if (isAdmin && companyId !== undefined) { + updatePayload.company_id = companyId + } + const { data: order, error: orderError } = await admin .from('orders') - .update({ - end_customer_id: endCustomerId ?? null, - total_price: orderSnapshot.total, - customer_data: customerSnapshot, - order_data: orderSnapshot, - order_hash: orderHash, - }) + .update(updatePayload) .eq('id', orderId) .select() .single() diff --git a/shop/lib/types.ts b/shop/lib/types.ts index def5159..ae74336 100644 --- a/shop/lib/types.ts +++ b/shop/lib/types.ts @@ -149,6 +149,7 @@ export type OrderSnapshot = { export type Order = { id: string user_id: string | null + company_id: string | null order_number: string order_hash: string | null end_customer_id: string | null diff --git a/shop/supabase/migrations/20260704161000_add_company_id_to_orders.sql b/shop/supabase/migrations/20260704161000_add_company_id_to_orders.sql new file mode 100644 index 0000000..2ec4dd7 --- /dev/null +++ b/shop/supabase/migrations/20260704161000_add_company_id_to_orders.sql @@ -0,0 +1,28 @@ +-- Migration: Add company_id to public.orders and update RLS policies + +-- 1. Add company_id column to orders table +ALTER TABLE public.orders + ADD COLUMN IF NOT EXISTS company_id UUID REFERENCES public.companies(id) ON DELETE SET NULL; + +-- 2. Backfill company_id from users for existing orders +UPDATE public.orders o +SET company_id = (SELECT company_id FROM public.users u WHERE u.id = o.user_id) +WHERE o.company_id IS NULL AND o.user_id IS NOT NULL; + +-- 3. Recreate RLS policies for public.orders to use orders.company_id +DROP POLICY IF EXISTS "Users can view their own orders" ON public.orders; +CREATE POLICY "Users can view their own orders" ON public.orders FOR SELECT USING ( + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR company_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + OR auth.uid() = user_id +); + +DROP POLICY IF EXISTS "Users can update their own orders" ON public.orders; +CREATE POLICY "Users can update their own orders" ON public.orders FOR UPDATE USING ( + (SELECT role FROM public.users WHERE id = auth.uid()) = 'admin' + OR company_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + OR auth.uid() = user_id +); + +-- Reload schema cache +NOTIFY pgrst, 'reload schema';