Ohne Query.limit liefert Appwrite nur 25 Dokumente. Bei 110 Kunden fehlten in WOMS alle Kunden ab 'Autowerkstatt ...' alphabetisch - neu angelegte Leads wirkten dadurch, als waeren sie nie angelegt worden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
269 lines
7.8 KiB
JavaScript
269 lines
7.8 KiB
JavaScript
import { config } from '../config.js'
|
|
import {
|
|
createAppwriteUser,
|
|
createDocument,
|
|
deleteDocument,
|
|
getCustomerByEmail,
|
|
getDocument,
|
|
getPortalAccessByCustomerId,
|
|
listDocuments,
|
|
updateAppwriteUserEmail,
|
|
updateAppwriteUserPassword,
|
|
deleteAppwriteUser,
|
|
updateDocument,
|
|
Query,
|
|
} from './appwriteAdmin.js'
|
|
|
|
function readPortalPassword(customer) {
|
|
if (customer?.portalPassword) return customer.portalPassword
|
|
// Fallback: Altbestand, bei dem das Passwort noch in den Notizen lag
|
|
const notes = customer?.notes || ''
|
|
const match = notes.match(/^__portalPassword__:([^\n]+)/)
|
|
return match ? match[1] : ''
|
|
}
|
|
|
|
function notesWithoutPortalPassword(notes) {
|
|
return (notes || '').replace(/^__portalPassword__:[^\n]*\n?/, '').trim()
|
|
}
|
|
|
|
function sanitizeCustomer(customer, { portalPassword } = {}) {
|
|
const storedPassword = portalPassword || readPortalPassword(customer)
|
|
return {
|
|
$id: customer.$id,
|
|
code: customer.code || '',
|
|
name: customer.name || '',
|
|
location: customer.location || '',
|
|
email: customer.email || '',
|
|
phone: customer.phone || '',
|
|
companyName: customer.companyName || '',
|
|
portalAccessEnabled: Boolean(customer.portalAccessEnabled),
|
|
appwriteUserId: customer.appwriteUserId || '',
|
|
customerStatus: customer.customerStatus || '',
|
|
archived: Boolean(customer.archived),
|
|
portalPassword: storedPassword || '',
|
|
}
|
|
}
|
|
|
|
async function ensurePortalAccess(customerId, appwriteUserId) {
|
|
const now = new Date().toISOString()
|
|
let portalAccess = await getPortalAccessByCustomerId(customerId)
|
|
if (portalAccess) {
|
|
portalAccess = await updateDocument(config.collections.customerPortalAccess, portalAccess.$id, {
|
|
enabled: true,
|
|
appwriteUserId,
|
|
passwordSet: true,
|
|
})
|
|
} else {
|
|
portalAccess = await createDocument(config.collections.customerPortalAccess, {
|
|
customerId,
|
|
enabled: true,
|
|
appwriteUserId,
|
|
passwordSet: true,
|
|
})
|
|
}
|
|
return portalAccess
|
|
}
|
|
|
|
export async function createCustomerWithPortalAccess(payload) {
|
|
const {
|
|
code = '',
|
|
name,
|
|
location = '',
|
|
email,
|
|
phone = '',
|
|
password,
|
|
} = payload || {}
|
|
|
|
if (!name?.trim()) {
|
|
const error = new Error('Name ist erforderlich')
|
|
error.status = 400
|
|
throw error
|
|
}
|
|
if (!email?.trim()) {
|
|
const error = new Error('E-Mail ist erforderlich')
|
|
error.status = 400
|
|
throw error
|
|
}
|
|
if (!password || password.length < 8) {
|
|
const error = new Error('Passwort muss mindestens 8 Zeichen haben')
|
|
error.status = 400
|
|
throw error
|
|
}
|
|
|
|
const existing = await getCustomerByEmail(email.trim())
|
|
if (existing) {
|
|
const error = new Error('Ein Kunde mit dieser E-Mail existiert bereits')
|
|
error.status = 409
|
|
throw error
|
|
}
|
|
|
|
const appwriteUser = await createAppwriteUser({
|
|
email: email.trim(),
|
|
password,
|
|
name: name.trim(),
|
|
})
|
|
|
|
const now = new Date().toISOString()
|
|
let customer
|
|
try {
|
|
customer = await createDocument(config.collections.customers, {
|
|
code: code.trim(),
|
|
name: name.trim(),
|
|
location: location.trim(),
|
|
email: email.trim(),
|
|
phone: phone.trim(),
|
|
portalPassword: password,
|
|
portalAccessEnabled: true,
|
|
appwriteUserId: appwriteUser.$id,
|
|
customerStatus: 'active',
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
await ensurePortalAccess(customer.$id, appwriteUser.$id)
|
|
} catch (err) {
|
|
try {
|
|
await deleteAppwriteUser(appwriteUser.$id)
|
|
} catch {
|
|
/* ignore rollback failure */
|
|
}
|
|
throw err
|
|
}
|
|
|
|
return {
|
|
customer: sanitizeCustomer(customer, { portalPassword: password }),
|
|
}
|
|
}
|
|
|
|
export async function updateCustomerWithPortalAccess(customerId, payload) {
|
|
const customer = await getDocument(config.collections.customers, customerId).catch(() => null)
|
|
if (!customer) {
|
|
const error = new Error('Kunde nicht gefunden')
|
|
error.status = 404
|
|
throw error
|
|
}
|
|
|
|
const {
|
|
code,
|
|
name,
|
|
location,
|
|
email,
|
|
phone,
|
|
password,
|
|
} = payload || {}
|
|
|
|
const nextEmail = (email ?? customer.email ?? '').trim()
|
|
if (!nextEmail) {
|
|
const error = new Error('E-Mail ist erforderlich')
|
|
error.status = 400
|
|
throw error
|
|
}
|
|
if (password && password.length < 8) {
|
|
const error = new Error('Passwort muss mindestens 8 Zeichen haben')
|
|
error.status = 400
|
|
throw error
|
|
}
|
|
|
|
const normalizedPassword =
|
|
typeof password === 'string' && password.trim().length > 0 ? password : undefined
|
|
|
|
if (nextEmail.toLowerCase() !== (customer.email || '').trim().toLowerCase()) {
|
|
const existing = await getCustomerByEmail(nextEmail)
|
|
if (existing && existing.$id !== customerId) {
|
|
const error = new Error('Ein anderer Kunde nutzt diese E-Mail bereits')
|
|
error.status = 409
|
|
throw error
|
|
}
|
|
}
|
|
|
|
let appwriteUserId = customer.appwriteUserId || ''
|
|
let portalPassword
|
|
|
|
if (!appwriteUserId && nextEmail && normalizedPassword) {
|
|
const appwriteUser = await createAppwriteUser({
|
|
email: nextEmail,
|
|
password: normalizedPassword,
|
|
name: (name ?? customer.name ?? nextEmail).trim(),
|
|
})
|
|
appwriteUserId = appwriteUser.$id
|
|
portalPassword = normalizedPassword
|
|
} else if (appwriteUserId) {
|
|
if (nextEmail && nextEmail !== customer.email) {
|
|
await updateAppwriteUserEmail(appwriteUserId, nextEmail)
|
|
}
|
|
if (normalizedPassword) {
|
|
await updateAppwriteUserPassword(appwriteUserId, normalizedPassword)
|
|
portalPassword = normalizedPassword
|
|
}
|
|
} else if (normalizedPassword) {
|
|
const error = new Error('Portal-Login fehlt � bitte E-Mail setzen und Passwort erneut speichern')
|
|
error.status = 400
|
|
throw error
|
|
}
|
|
|
|
const now = new Date().toISOString()
|
|
const nextPortalPassword =
|
|
normalizedPassword || readPortalPassword(customer) || undefined
|
|
// Legacy-Prefix aus den Notizen entfernen, Passwort liegt jetzt im eigenen Feld
|
|
const cleanedNotes = notesWithoutPortalPassword(customer.notes)
|
|
const notesChanged = cleanedNotes !== (customer.notes || '')
|
|
|
|
const updated = await updateDocument(config.collections.customers, customerId, {
|
|
...(code !== undefined ? { code: String(code).trim() } : {}),
|
|
...(name !== undefined ? { name: String(name).trim() } : {}),
|
|
...(location !== undefined ? { location: String(location).trim() } : {}),
|
|
email: nextEmail,
|
|
...(phone !== undefined ? { phone: String(phone).trim() } : {}),
|
|
...(nextPortalPassword ? { portalPassword: nextPortalPassword } : {}),
|
|
...(notesChanged ? { notes: cleanedNotes } : {}),
|
|
// CRM-Stufe (lead/customer/lost) nicht ueberschreiben - nur setzen, wenn leer
|
|
...(appwriteUserId
|
|
? {
|
|
appwriteUserId,
|
|
portalAccessEnabled: true,
|
|
...(customer.customerStatus ? {} : { customerStatus: 'active' }),
|
|
}
|
|
: {}),
|
|
updatedAt: now,
|
|
})
|
|
|
|
if (appwriteUserId) {
|
|
await ensurePortalAccess(customerId, appwriteUserId)
|
|
}
|
|
|
|
return {
|
|
customer: sanitizeCustomer(updated, portalPassword ? { portalPassword } : {}),
|
|
}
|
|
}
|
|
|
|
export async function listCustomersForAdmin() {
|
|
// Ohne Limit liefert Appwrite nur 25 Dokumente - neue Kunden fielen dann aus der Liste.
|
|
const customers = await listDocuments(config.collections.customers, [
|
|
Query.orderAsc('name'),
|
|
Query.limit(5000),
|
|
])
|
|
return customers.map((customer) => sanitizeCustomer(customer))
|
|
}
|
|
|
|
export async function deleteCustomerWithPortalAccess(customerId) {
|
|
const customer = await getDocument(config.collections.customers, customerId)
|
|
.catch(() => null)
|
|
if (!customer) {
|
|
const error = new Error('Kunde nicht gefunden')
|
|
error.status = 404
|
|
throw error
|
|
}
|
|
|
|
const portalAccess = await getPortalAccessByCustomerId(customerId)
|
|
if (portalAccess) {
|
|
await deleteDocument(
|
|
config.collections.customerPortalAccess,
|
|
portalAccess.$id
|
|
)
|
|
}
|
|
await deleteDocument(config.collections.customers, customerId)
|
|
if (customer.appwriteUserId) {
|
|
await deleteAppwriteUser(customer.appwriteUserId).catch(() => {})
|
|
}
|
|
return { success: true }
|
|
}
|