feat: link end customers directly to companies
All checks were successful
Staging Build / build (push) Successful in 3m37s
All checks were successful
Staging Build / build (push) Successful in 3m37s
This commit is contained in:
@@ -98,7 +98,7 @@ export default function CompanyCustomersPage() {
|
|||||||
setZip(customer ? customer.zip || '' : '')
|
setZip(customer ? customer.zip || '' : '')
|
||||||
setCity(customer ? customer.city || '' : '')
|
setCity(customer ? customer.city || '' : '')
|
||||||
setVatId(customer ? customer.vat_id || '' : '')
|
setVatId(customer ? customer.vat_id || '' : '')
|
||||||
setPartnerId(customer ? customer.partner_id || '' : companyId)
|
setPartnerId(companyId)
|
||||||
setEmail(customer ? customer.email || '' : '')
|
setEmail(customer ? customer.email || '' : '')
|
||||||
setError(null)
|
setError(null)
|
||||||
setIsOpen(true)
|
setIsOpen(true)
|
||||||
@@ -106,15 +106,11 @@ export default function CompanyCustomersPage() {
|
|||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!partnerId) {
|
|
||||||
setError('Bitte weisen Sie einen Partner zu.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setLoadingAction(true)
|
setLoadingAction(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
partner_id: partnerId,
|
partner_id: companyId,
|
||||||
company_name: companyName.trim(),
|
company_name: companyName.trim(),
|
||||||
first_name: firstName.trim() || undefined,
|
first_name: firstName.trim() || undefined,
|
||||||
last_name: lastName.trim() || undefined,
|
last_name: lastName.trim() || undefined,
|
||||||
@@ -415,21 +411,14 @@ export default function CompanyCustomersPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="cust-partner">Zugeordneter Partner *</Label>
|
<Label htmlFor="cust-partner">Zugeordnetes Unternehmen</Label>
|
||||||
<select
|
<Input
|
||||||
id="cust-partner"
|
id="cust-partner"
|
||||||
required
|
type="text"
|
||||||
value={partnerId}
|
disabled
|
||||||
onChange={(e) => setPartnerId(e.target.value)}
|
value={company ? company.name : ''}
|
||||||
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"
|
className="bg-slate-100 dark:bg-white/10 border-slate-200 dark:border-white/10 cursor-not-allowed"
|
||||||
>
|
/>
|
||||||
<option value="" disabled className="bg-white dark:bg-slate-950 text-slate-900 dark:text-white">Partner auswählen...</option>
|
|
||||||
{partners.map((p) => (
|
|
||||||
<option key={p.id} value={p.id} className="bg-white dark:bg-slate-950 text-slate-900 dark:text-white">
|
|
||||||
{p.name} ({p.email})
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -59,25 +59,11 @@ export async function assignCompanyToUser(userId: string, companyId: string | nu
|
|||||||
|
|
||||||
export async function getEndCustomersByCompany(companyId: string) {
|
export async function getEndCustomersByCompany(companyId: string) {
|
||||||
const admin = createAdminClient()
|
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
|
const { data: customers, error: customersError } = await admin
|
||||||
.from('end_customers')
|
.from('end_customers')
|
||||||
.select('*')
|
.select('*')
|
||||||
.in('partner_id', partnerIds)
|
.eq('partner_id', companyId)
|
||||||
if (customersError) throw customersError
|
if (customersError) throw customersError
|
||||||
|
|
||||||
return customers || []
|
return customers || []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,44 +77,24 @@ export async function getCompanyDetails(companyId: string) {
|
|||||||
export async function getEndCustomersByCompanyWithPartners(companyId: string) {
|
export async function getEndCustomersByCompanyWithPartners(companyId: string) {
|
||||||
const admin = createAdminClient()
|
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
|
const { data: customers, error: customersError } = await admin
|
||||||
.from('end_customers')
|
.from('end_customers')
|
||||||
.select('*')
|
.select('*')
|
||||||
.in('partner_id', partnerIds)
|
.eq('partner_id', companyId)
|
||||||
if (customersError) throw customersError
|
if (customersError) throw customersError
|
||||||
|
|
||||||
// 3. Get profiles of all these partners to resolve their names
|
const { data: company } = await admin
|
||||||
const { data: profiles, error: profilesError } = await admin
|
.from('companies')
|
||||||
.from('profiles')
|
.select('name')
|
||||||
.select('id, first_name, last_name')
|
.eq('id', companyId)
|
||||||
.in('id', partnerIds)
|
.single()
|
||||||
if (profilesError) throw profilesError
|
|
||||||
|
|
||||||
// Get user emails
|
const companyName = company?.name || 'Unbekannt'
|
||||||
const { data: { users }, error: authError } = await admin.auth.admin.listUsers()
|
|
||||||
const emailMap = new Map((users || []).map(u => [u.id, u.email || '']))
|
|
||||||
|
|
||||||
const profileMap = new Map((profiles || []).map((p) => [p.id, p]))
|
return customers.map((c) => ({
|
||||||
|
...c,
|
||||||
return customers.map((c) => {
|
partner_name: companyName,
|
||||||
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',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCompanyPartners(companyId: string) {
|
export async function getCompanyPartners(companyId: string) {
|
||||||
|
|||||||
@@ -30,9 +30,23 @@ export type EndCustomerFormData = {
|
|||||||
*/
|
*/
|
||||||
export async function getEndCustomers(): Promise<EndCustomer[]> {
|
export async function getEndCustomers(): Promise<EndCustomer[]> {
|
||||||
const supabase = await createClient()
|
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
|
const { data, error } = await supabase
|
||||||
.from('end_customers')
|
.from('end_customers')
|
||||||
.select('*')
|
.select('*')
|
||||||
|
.eq('partner_id', dbUser.company_id)
|
||||||
.order('company_name', { ascending: true })
|
.order('company_name', { ascending: true })
|
||||||
|
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
@@ -66,10 +80,20 @@ export async function createEndCustomer(formData: EndCustomerFormData): Promise<
|
|||||||
const { data: { user } } = await supabase.auth.getUser()
|
const { data: { user } } = await supabase.auth.getUser()
|
||||||
if (!user) throw new Error('Not authenticated')
|
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
|
const { data, error } = await supabase
|
||||||
.from('end_customers')
|
.from('end_customers')
|
||||||
.insert([{
|
.insert([{
|
||||||
partner_id: user.id,
|
partner_id: dbUser.company_id,
|
||||||
company_name: formData.company_name,
|
company_name: formData.company_name,
|
||||||
vat_id: formData.vat_id || null,
|
vat_id: formData.vat_id || null,
|
||||||
first_name: formData.first_name || null,
|
first_name: formData.first_name || null,
|
||||||
@@ -167,10 +191,18 @@ export async function anonymizeEndCustomer(id: string): Promise<void> {
|
|||||||
|
|
||||||
export async function getPartnerEndCustomers(partnerId: string): Promise<EndCustomer[]> {
|
export async function getPartnerEndCustomers(partnerId: string): Promise<EndCustomer[]> {
|
||||||
const admin = createAdminClient()
|
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
|
const { data, error } = await admin
|
||||||
.from('end_customers')
|
.from('end_customers')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('partner_id', partnerId)
|
.eq('partner_id', targetId)
|
||||||
.order('company_name', { ascending: true })
|
.order('company_name', { ascending: true })
|
||||||
|
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ BEGIN
|
|||||||
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'end_customers') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'end_customers') THEN
|
||||||
CREATE TABLE public.end_customers (
|
CREATE TABLE public.end_customers (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
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,
|
company_name TEXT NOT NULL,
|
||||||
vat_id TEXT,
|
vat_id TEXT,
|
||||||
first_name TEXT,
|
first_name TEXT,
|
||||||
@@ -153,7 +153,7 @@ BEGIN
|
|||||||
|
|
||||||
-- end_customers Policies
|
-- end_customers Policies
|
||||||
DROP POLICY IF EXISTS "Partner sehen nur eigene Endkunden" ON public.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 (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
|
-- pos_modules Policies
|
||||||
DROP POLICY IF EXISTS "Allow authenticated read on pos_modules" ON public.pos_modules;
|
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 (
|
CREATE POLICY "Partner sehen Lizenzen eigener Kunden" ON public.licenses FOR SELECT USING (
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1 FROM public.end_customers c
|
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;
|
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 (
|
CREATE POLICY "Partner erstellen Lizenzen eigener Kunden" ON public.licenses FOR INSERT WITH CHECK (
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1 FROM public.end_customers c
|
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())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
Reference in New Issue
Block a user