From 5f65f890ee9d6afdf85ecab15f6ca0ead6f8de4f Mon Sep 17 00:00:00 2001 From: DanielS Date: Sat, 4 Jul 2026 15:17:04 +0200 Subject: [PATCH] feat: link end customers directly to companies --- .../admin/companies/[id]/customers/page.tsx | 29 +++------- shop/lib/actions/companies.ts | 58 ++++--------------- shop/lib/actions/end-customers.ts | 36 +++++++++++- .../20260624000000_integrity_functions.sql | 8 +-- ...2000_update_end_customers_partner_fkey.sql | 51 ++++++++++++++++ 5 files changed, 110 insertions(+), 72 deletions(-) create mode 100644 shop/supabase/migrations/20260704152000_update_end_customers_partner_fkey.sql diff --git a/shop/app/admin/companies/[id]/customers/page.tsx b/shop/app/admin/companies/[id]/customers/page.tsx index eb9f114..46e56a1 100644 --- a/shop/app/admin/companies/[id]/customers/page.tsx +++ b/shop/app/admin/companies/[id]/customers/page.tsx @@ -98,7 +98,7 @@ export default function CompanyCustomersPage() { setZip(customer ? customer.zip || '' : '') setCity(customer ? customer.city || '' : '') setVatId(customer ? customer.vat_id || '' : '') - setPartnerId(customer ? customer.partner_id || '' : companyId) + setPartnerId(companyId) setEmail(customer ? customer.email || '' : '') setError(null) setIsOpen(true) @@ -106,15 +106,11 @@ export default function CompanyCustomersPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!partnerId) { - setError('Bitte weisen Sie einen Partner zu.') - return - } setLoadingAction(true) setError(null) try { const payload = { - partner_id: partnerId, + partner_id: companyId, company_name: companyName.trim(), first_name: firstName.trim() || undefined, last_name: lastName.trim() || undefined, @@ -415,21 +411,14 @@ export default function CompanyCustomersPage() {
- - setPartnerId(e.target.value)} - className="flex h-9 w-full rounded-md border border-slate-200 dark:border-white/10 bg-slate-50 dark:bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-50 text-slate-900 dark:text-slate-200" - > - - {partners.map((p) => ( - - ))} - + type="text" + disabled + value={company ? company.name : ''} + className="bg-slate-100 dark:bg-white/10 border-slate-200 dark:border-white/10 cursor-not-allowed" + />
diff --git a/shop/lib/actions/companies.ts b/shop/lib/actions/companies.ts index 527d1f2..3eef828 100644 --- a/shop/lib/actions/companies.ts +++ b/shop/lib/actions/companies.ts @@ -59,25 +59,11 @@ export async function assignCompanyToUser(userId: string, companyId: string | nu export async function getEndCustomersByCompany(companyId: string) { const admin = createAdminClient() - - // 1. Get all partners of this company - const { data: partners, error: partnersError } = await admin - .from('users') - .select('id') - .eq('company_id', companyId) - if (partnersError) throw partnersError - - if (!partners || partners.length === 0) return [] - - const partnerIds = partners.map((p) => p.id) - - // 2. Get all end customers belonging to these partners const { data: customers, error: customersError } = await admin .from('end_customers') .select('*') - .in('partner_id', partnerIds) + .eq('partner_id', companyId) if (customersError) throw customersError - return customers || [] } @@ -91,44 +77,24 @@ export async function getCompanyDetails(companyId: string) { export async function getEndCustomersByCompanyWithPartners(companyId: string) { const admin = createAdminClient() - // 1. Get all partners of this company - const { data: partners, error: partnersError } = await admin - .from('users') - .select('id') - .eq('company_id', companyId) - if (partnersError) throw partnersError - - if (!partners || partners.length === 0) return [] - - const partnerIds = partners.map((p) => p.id) - - // 2. Get all end customers belonging to these partners const { data: customers, error: customersError } = await admin .from('end_customers') .select('*') - .in('partner_id', partnerIds) + .eq('partner_id', companyId) if (customersError) throw customersError - // 3. Get profiles of all these partners to resolve their names - const { data: profiles, error: profilesError } = await admin - .from('profiles') - .select('id, first_name, last_name') - .in('id', partnerIds) - if (profilesError) throw profilesError + const { data: company } = await admin + .from('companies') + .select('name') + .eq('id', companyId) + .single() - // Get user emails - const { data: { users }, error: authError } = await admin.auth.admin.listUsers() - const emailMap = new Map((users || []).map(u => [u.id, u.email || ''])) + const companyName = company?.name || 'Unbekannt' - const profileMap = new Map((profiles || []).map((p) => [p.id, p])) - - return customers.map((c) => { - const prof = profileMap.get(c.partner_id) - return { - ...c, - partner_name: prof ? `${prof.first_name || ''} ${prof.last_name || ''}`.trim() || emailMap.get(c.partner_id) || 'Unbekannt' : emailMap.get(c.partner_id) || 'Unbekannt', - } - }) + return customers.map((c) => ({ + ...c, + partner_name: companyName, + })) } export async function getCompanyPartners(companyId: string) { diff --git a/shop/lib/actions/end-customers.ts b/shop/lib/actions/end-customers.ts index a88eac3..e52ce57 100644 --- a/shop/lib/actions/end-customers.ts +++ b/shop/lib/actions/end-customers.ts @@ -30,9 +30,23 @@ export type EndCustomerFormData = { */ export async function getEndCustomers(): Promise { const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) throw new Error('Not authenticated') + + const { data: dbUser, error: dbUserError } = await supabase + .from('users') + .select('company_id') + .eq('id', user.id) + .single() + + if (dbUserError || !dbUser || !dbUser.company_id) { + return [] + } + const { data, error } = await supabase .from('end_customers') .select('*') + .eq('partner_id', dbUser.company_id) .order('company_name', { ascending: true }) if (error) throw error @@ -66,10 +80,20 @@ export async function createEndCustomer(formData: EndCustomerFormData): Promise< const { data: { user } } = await supabase.auth.getUser() if (!user) throw new Error('Not authenticated') + const { data: dbUser, error: dbUserError } = await supabase + .from('users') + .select('company_id') + .eq('id', user.id) + .single() + + if (dbUserError || !dbUser || !dbUser.company_id) { + throw new Error('Kein Unternehmen zugewiesen.') + } + const { data, error } = await supabase .from('end_customers') .insert([{ - partner_id: user.id, + partner_id: dbUser.company_id, company_name: formData.company_name, vat_id: formData.vat_id || null, first_name: formData.first_name || null, @@ -167,10 +191,18 @@ export async function anonymizeEndCustomer(id: string): Promise { export async function getPartnerEndCustomers(partnerId: string): Promise { const admin = createAdminClient() + const { data: dbUser } = await admin + .from('users') + .select('company_id') + .eq('id', partnerId) + .single() + + const targetId = dbUser?.company_id || partnerId + const { data, error } = await admin .from('end_customers') .select('*') - .eq('partner_id', partnerId) + .eq('partner_id', targetId) .order('company_name', { ascending: true }) if (error) throw error diff --git a/shop/supabase/migrations/20260624000000_integrity_functions.sql b/shop/supabase/migrations/20260624000000_integrity_functions.sql index 3527fa9..01d2551 100644 --- a/shop/supabase/migrations/20260624000000_integrity_functions.sql +++ b/shop/supabase/migrations/20260624000000_integrity_functions.sql @@ -96,7 +96,7 @@ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'end_customers') THEN CREATE TABLE public.end_customers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - partner_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + partner_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, company_name TEXT NOT NULL, vat_id TEXT, first_name TEXT, @@ -153,7 +153,7 @@ BEGIN -- end_customers Policies 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 ALL USING (auth.uid() = partner_id) WITH CHECK (auth.uid() = partner_id); + CREATE POLICY "Partner sehen nur eigene Endkunden" ON public.end_customers FOR ALL USING (partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid())) WITH CHECK (partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid())); -- pos_modules Policies DROP POLICY IF EXISTS "Allow authenticated read on pos_modules" ON public.pos_modules; @@ -164,14 +164,14 @@ BEGIN CREATE POLICY "Partner sehen Lizenzen eigener Kunden" ON public.licenses FOR SELECT USING ( EXISTS ( SELECT 1 FROM public.end_customers c - WHERE c.id = licenses.end_customer_id AND c.partner_id = auth.uid() + WHERE c.id = licenses.end_customer_id AND c.partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) ) ); DROP POLICY IF EXISTS "Partner erstellen Lizenzen eigener Kunden" ON public.licenses; CREATE POLICY "Partner erstellen Lizenzen eigener Kunden" ON public.licenses FOR INSERT WITH CHECK ( EXISTS ( SELECT 1 FROM public.end_customers c - WHERE c.id = licenses.end_customer_id AND c.partner_id = auth.uid() + WHERE c.id = licenses.end_customer_id AND c.partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) ) ); diff --git a/shop/supabase/migrations/20260704152000_update_end_customers_partner_fkey.sql b/shop/supabase/migrations/20260704152000_update_end_customers_partner_fkey.sql new file mode 100644 index 0000000..17a98c3 --- /dev/null +++ b/shop/supabase/migrations/20260704152000_update_end_customers_partner_fkey.sql @@ -0,0 +1,51 @@ +-- Migration: Update end_customers.partner_id to reference public.companies.id +-- and update RLS policies accordingly. + +-- 1. Drop old foreign key constraint if exists +ALTER TABLE public.end_customers + DROP CONSTRAINT IF EXISTS end_customers_partner_id_fkey; + +-- 2. Add new foreign key constraint to public.companies(id) +-- Note: the column is still called partner_id, but references companies now. +ALTER TABLE public.end_customers + ADD CONSTRAINT end_customers_partner_id_fkey + FOREIGN KEY (partner_id) + REFERENCES public.companies(id) + ON DELETE CASCADE; + +-- 3. Recreate RLS policies for end_customers +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 ALL + USING ( + partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ) + WITH CHECK ( + partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ); + +-- 4. Recreate RLS policies for licenses (since partner_id now matches company_id) +DROP POLICY IF EXISTS "Partner sehen Lizenzen eigener Kunden" ON public.licenses; +CREATE POLICY "Partner sehen Lizenzen eigener Kunden" ON public.licenses + FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM public.end_customers c + WHERE c.id = licenses.end_customer_id + AND c.partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ) + ); + +DROP POLICY IF EXISTS "Partner erstellen Lizenzen eigener Kunden" ON public.licenses; +CREATE POLICY "Partner erstellen Lizenzen eigener Kunden" ON public.licenses + FOR INSERT + WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.end_customers c + WHERE c.id = licenses.end_customer_id + AND c.partner_id = (SELECT company_id FROM public.users WHERE id = auth.uid()) + ) + ); + +-- Reload Schema Cache +NOTIFY pgrst, 'reload schema';