289 lines
7.9 KiB
TypeScript
289 lines
7.9 KiB
TypeScript
'use server'
|
|
|
|
import { createClient } from '@/lib/supabase/server'
|
|
import { createAdminClient } from '@/lib/supabase/admin'
|
|
import type { Order, EndCustomer, EndCustomerWithOrders, OrderWithRegisterName, FlattenedDevice, EndCustomerWithDevices, OrderItem } from '@/lib/types'
|
|
|
|
/**
|
|
* Holt alle Bestellanfragen.
|
|
* RLS filtert automatisch auf die eigene Firma (bzw. lässt Admins alle sehen).
|
|
*/
|
|
export async function getCompanyOrders(): Promise<Order[]> {
|
|
const supabase = await createClient()
|
|
|
|
const { data, error } = await supabase
|
|
.from('orders')
|
|
.select('*')
|
|
.order('created_at', { ascending: false })
|
|
|
|
if (error) throw error
|
|
return data as Order[]
|
|
}
|
|
|
|
/**
|
|
* Holt alle Endkunden.
|
|
* RLS filtert automatisch auf die eigene Firma (bzw. lässt Admins alle sehen).
|
|
*/
|
|
export async function getCompanyEndCustomers(): 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 alle Endkunden inkl. ihrer Bestellungen/Kassen verschachtelt.
|
|
* Extrahiert den Kassen-Namen aus order_data.items[0].device_name (Fallback "Kasse 1").
|
|
* RLS filtert automatisch über get_auth_company_id().
|
|
*/
|
|
export async function getPartnerCustomersWithOrders(): Promise<EndCustomerWithOrders[]> {
|
|
const supabase = await createClient()
|
|
|
|
const { data, error } = await supabase
|
|
.from('end_customers')
|
|
.select('*, orders(*)')
|
|
.order('company_name', { ascending: true })
|
|
|
|
if (error) throw error
|
|
|
|
return (data || []).map((customer: any) => {
|
|
const rawOrders = customer.orders || []
|
|
// Sort orders by created_at desc
|
|
const sortedOrders = [...rawOrders].sort(
|
|
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
)
|
|
|
|
const orders: OrderWithRegisterName[] = sortedOrders.map((order: any) => {
|
|
const items = order.order_data?.items || []
|
|
const deviceName = items[0]?.device_name || 'Kasse 1'
|
|
return {
|
|
...order,
|
|
register_name: deviceName,
|
|
}
|
|
})
|
|
|
|
return {
|
|
...customer,
|
|
orders,
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Bereinigt Kassen-Präfixe aus Produkt- und Modulnamen.
|
|
* Entfernt Muster wie "Kasse1:", "kass2:", "kasse 1:" etc.
|
|
*/
|
|
function stripDevicePrefix(name: string): string {
|
|
return name.replace(/^kasse?\s*\d*\s*:\s*/i, '').trim()
|
|
}
|
|
|
|
/**
|
|
* Holt alle Endkunden mit geflatteteten Kassen (eine Kasse = ein FlattenedDevice).
|
|
* Jede Order wird nach device_name in einzelne Kassen aufgespalten.
|
|
* RLS filtert automatisch über get_auth_company_id().
|
|
*/
|
|
export async function getPartnerCustomersWithDevices(): Promise<EndCustomerWithDevices[]> {
|
|
const supabase = await createClient()
|
|
|
|
const { data, error } = await supabase
|
|
.from('end_customers')
|
|
.select('*, orders(*)')
|
|
.order('company_name', { ascending: true })
|
|
|
|
if (error) throw error
|
|
|
|
return (data || []).map((customer: any) => {
|
|
const rawOrders = customer.orders || []
|
|
const sortedOrders = [...rawOrders].sort(
|
|
(a: any, b: any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
)
|
|
|
|
const devices: FlattenedDevice[] = []
|
|
|
|
for (const order of sortedOrders) {
|
|
const allItems: OrderItem[] = order.order_data?.items || []
|
|
const totalOrderPrice: number = order.total_price || 0
|
|
|
|
// Gruppiere Items nach device_name
|
|
const deviceGroups: Record<string, OrderItem[]> = {}
|
|
for (const item of allItems) {
|
|
const key = item.device_name || 'Kasse 1'
|
|
if (!deviceGroups[key]) deviceGroups[key] = []
|
|
deviceGroups[key].push(item)
|
|
}
|
|
|
|
const deviceKeys = Object.keys(deviceGroups)
|
|
|
|
for (const deviceKey of deviceKeys) {
|
|
const devItems = deviceGroups[deviceKey]
|
|
|
|
// Bereinige Produkt- und Modulnamen
|
|
const cleanedItems: OrderItem[] = devItems.map(item => ({
|
|
...item,
|
|
product_name: stripDevicePrefix(item.product_name),
|
|
selected_modules: (item.selected_modules || []).map(mod => ({
|
|
...mod,
|
|
module_name: stripDevicePrefix(mod.module_name),
|
|
})),
|
|
}))
|
|
|
|
// Anteiliger Preis dieser Kasse (gleichmäßige Aufteilung)
|
|
const devicePrice = deviceKeys.length > 1
|
|
? totalOrderPrice / deviceKeys.length
|
|
: totalOrderPrice
|
|
|
|
devices.push({
|
|
orderId: order.id,
|
|
orderNumber: order.order_number,
|
|
orderStatus: order.status,
|
|
pdfUrl: order.pdf_url,
|
|
createdAt: order.created_at,
|
|
deviceId: deviceKey,
|
|
deviceName: deviceKey,
|
|
items: cleanedItems,
|
|
totalPrice: Math.round(devicePrice * 100) / 100,
|
|
})
|
|
}
|
|
}
|
|
|
|
const { orders: _orders, ...customerWithoutOrders } = customer
|
|
return {
|
|
...customerWithoutOrders,
|
|
devices,
|
|
} as EndCustomerWithDevices
|
|
})
|
|
}
|
|
|
|
export interface GetCustomersForWizardParams {
|
|
partnerCompanyId?: string
|
|
page?: number
|
|
pageSize?: number
|
|
search?: string
|
|
}
|
|
|
|
export interface GetCustomersForWizardResult {
|
|
customers: EndCustomer[]
|
|
totalCount: number
|
|
totalPages: number
|
|
page: number
|
|
pageSize: number
|
|
isAdmin: boolean
|
|
}
|
|
|
|
/**
|
|
* Server Action für den Neubestellung-Wizard:
|
|
* Holt Endkunden rollenbasiert mit Partner-Filter (für Admins) und Pagination.
|
|
*/
|
|
export async function getCustomersForWizard(
|
|
params: GetCustomersForWizardParams = {}
|
|
): Promise<GetCustomersForWizardResult> {
|
|
const { partnerCompanyId, page = 1, pageSize = 10, search = '' } = params
|
|
const supabase = await createClient()
|
|
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser()
|
|
|
|
if (!user) {
|
|
throw new Error('Nicht authentifiziert')
|
|
}
|
|
|
|
const { data: dbUser } = await supabase
|
|
.from('users')
|
|
.select('role, company_id')
|
|
.eq('id', user.id)
|
|
.single()
|
|
|
|
const isAdmin = dbUser?.role === 'admin'
|
|
|
|
let client = supabase
|
|
if (isAdmin) {
|
|
try {
|
|
client = createAdminClient() as any
|
|
} catch {
|
|
client = supabase
|
|
}
|
|
}
|
|
|
|
let query = client
|
|
.from('end_customers')
|
|
.select('*', { count: 'exact' })
|
|
.eq('is_anonymized', false)
|
|
|
|
if (isAdmin) {
|
|
if (partnerCompanyId && partnerCompanyId !== 'all' && partnerCompanyId !== '') {
|
|
query = query.eq('partner_id', partnerCompanyId)
|
|
}
|
|
} else {
|
|
if (dbUser?.company_id) {
|
|
query = query.eq('partner_id', dbUser.company_id)
|
|
}
|
|
}
|
|
|
|
if (search && search.trim() !== '') {
|
|
const term = `%${search.trim()}%`
|
|
query = query.or(
|
|
`company_name.ilike.${term},first_name.ilike.${term},last_name.ilike.${term},city.ilike.${term},zip.ilike.${term}`
|
|
)
|
|
}
|
|
|
|
query = query.order('company_name', { ascending: true })
|
|
|
|
const validPageSize = [10, 20, 50].includes(pageSize) ? pageSize : 10
|
|
const validPage = Math.max(1, page)
|
|
const from = (validPage - 1) * validPageSize
|
|
const to = from + validPageSize - 1
|
|
|
|
query = query.range(from, to)
|
|
|
|
const { data, error, count } = await query
|
|
|
|
if (error) {
|
|
throw error
|
|
}
|
|
|
|
const totalCount = count ?? 0
|
|
const totalPages = Math.ceil(totalCount / validPageSize) || 1
|
|
|
|
return {
|
|
customers: (data as EndCustomer[]) || [],
|
|
totalCount,
|
|
totalPages,
|
|
page: validPage,
|
|
pageSize: validPageSize,
|
|
isAdmin,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a license number is already taken by any active order.
|
|
*/
|
|
export async function isLicenseNumberTaken(licenseNumber: string, excludeOrderId?: string): Promise<boolean> {
|
|
const normalized = licenseNumber?.trim().toUpperCase()
|
|
if (!normalized) return false
|
|
|
|
const supabase = await createClient()
|
|
let query = supabase
|
|
.from('orders')
|
|
.select('id')
|
|
.contains('order_snapshot', { items: [{ license_number: normalized }] })
|
|
|
|
if (excludeOrderId) {
|
|
query = query.neq('id', excludeOrderId)
|
|
}
|
|
|
|
const { data, error } = await query
|
|
if (error) {
|
|
console.error('Error checking license number:', error)
|
|
return false
|
|
}
|
|
return !!(data && data.length > 0)
|
|
}
|
|
|
|
|