feat(crm): end_customers table, RLS, wizard customer selector, /my-customers CRUD, GDPR anonymization
This commit is contained in:
145
shop/lib/actions/end-customers.ts
Normal file
145
shop/lib/actions/end-customers.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
'use server'
|
||||
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import type { EndCustomer } from '@/lib/types'
|
||||
|
||||
// ─── Typen ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export type EndCustomerFormData = {
|
||||
company_name: string
|
||||
vat_id?: string
|
||||
first_name?: string
|
||||
last_name?: string
|
||||
street?: string
|
||||
zip?: string
|
||||
city?: string
|
||||
}
|
||||
|
||||
// ─── Lesen ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Holt alle (nicht anonymisierten) Endkunden des eingeloggten Partners.
|
||||
* RLS stellt sicher, dass nur eigene Kunden zurückgegeben werden.
|
||||
*/
|
||||
export async function getEndCustomers(): Promise<EndCustomer[]> {
|
||||
const supabase = await createClient()
|
||||
const { data, error } = await supabase
|
||||
.from('end_customers')
|
||||
.select('*')
|
||||
.order('company_name', { ascending: true })
|
||||
|
||||
if (error) throw error
|
||||
return data as EndCustomer[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt einen einzelnen Endkunden (Eigentümerprüfung via RLS).
|
||||
*/
|
||||
export async function getEndCustomer(id: string): Promise<EndCustomer | null> {
|
||||
const supabase = await createClient()
|
||||
const { data, error } = await supabase
|
||||
.from('end_customers')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single()
|
||||
|
||||
if (error) return null
|
||||
return data as EndCustomer
|
||||
}
|
||||
|
||||
// ─── Erstellen ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Legt einen neuen Endkunden für den eingeloggten Partner an.
|
||||
* partner_id wird serverseitig aus der Session gesetzt (kein Client-Trust).
|
||||
*/
|
||||
export async function createEndCustomer(formData: EndCustomerFormData): Promise<EndCustomer> {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error('Not authenticated')
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('end_customers')
|
||||
.insert([{
|
||||
partner_id: user.id,
|
||||
company_name: formData.company_name,
|
||||
vat_id: formData.vat_id || null,
|
||||
first_name: formData.first_name || null,
|
||||
last_name: formData.last_name || null,
|
||||
street: formData.street || null,
|
||||
zip: formData.zip || null,
|
||||
city: formData.city || null,
|
||||
}])
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
revalidatePath('/my-customers')
|
||||
return data as EndCustomer
|
||||
}
|
||||
|
||||
// ─── Aktualisieren ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Aktualisiert Adressdaten eines Endkunden.
|
||||
* RLS verhindert Zugriff auf fremde Kunden.
|
||||
*/
|
||||
export async function updateEndCustomer(id: string, formData: EndCustomerFormData): Promise<void> {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase
|
||||
.from('end_customers')
|
||||
.update({
|
||||
company_name: formData.company_name,
|
||||
vat_id: formData.vat_id || null,
|
||||
first_name: formData.first_name || null,
|
||||
last_name: formData.last_name || null,
|
||||
street: formData.street || null,
|
||||
zip: formData.zip || null,
|
||||
city: formData.city || null,
|
||||
})
|
||||
.eq('id', id)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
revalidatePath('/my-customers')
|
||||
revalidatePath(`/my-customers/${id}`)
|
||||
}
|
||||
|
||||
// ─── DSGVO: Anonymisieren ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Anonymisiert einen Endkunden gemäß DSGVO „Recht auf Vergessenwerden".
|
||||
*
|
||||
* - Überschreibt alle personenbezogenen Felder mit [GELÖSCHT]
|
||||
* - Setzt is_anonymized = true (verhindert weitere Bearbeitung)
|
||||
* - LÖSCHT NICHT die Zeile (FK in orders bleibt erhalten)
|
||||
* - Bestell-Snapshots in orders.customer_data werden NICHT verändert
|
||||
* (steuerrechtliche Aufbewahrungspflicht)
|
||||
*/
|
||||
export async function anonymizeEndCustomer(id: string): Promise<void> {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase
|
||||
.from('end_customers')
|
||||
.update({
|
||||
company_name: '[GELÖSCHT]',
|
||||
vat_id: null,
|
||||
first_name: '[GELÖSCHT]',
|
||||
last_name: null,
|
||||
street: null,
|
||||
zip: null,
|
||||
city: null,
|
||||
is_anonymized: true,
|
||||
})
|
||||
.eq('id', id)
|
||||
.eq('is_anonymized', false) // Keine doppelte Anonymisierung
|
||||
|
||||
if (error) throw error
|
||||
|
||||
revalidatePath('/my-customers')
|
||||
revalidatePath(`/my-customers/${id}`)
|
||||
}
|
||||
Reference in New Issue
Block a user