- Zwei Kundenstufen: Lead (potenziell) und fester Kunde; Lead braucht nur Unternehmen + E-Mail und erzeugt automatisch ein Akquise-Ticket - Akquise-Worksheet-Ergebnis (In Kontakt/Zugesagt/Abgesagt) sichtbar in WSIDs; "Zugesagt" macht den Lead automatisch zum festen Kunden, "Abgesagt" -> verloren - Kunde wird nur in Appwrite gepflegt und automatisch als InvoiceNinja-Client angelegt/aktualisiert (beim Upgrade bzw. bei Datenpflege) - Rechnungen nur fuer feste Kunden mit vollstaendigen Daten (Strasse/PLZ/Stadt) - Kundenliste mit Stufen-Filter, neue Rechnungs-/Adressfelder Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
4.0 KiB
JavaScript
120 lines
4.0 KiB
JavaScript
import { databases, DATABASE_ID, COLLECTIONS, Query, ID } from './appwrite'
|
|
import { integrationsApi } from './integrationsApi'
|
|
|
|
/** Pflichtfelder, damit eine Rechnung erstellt werden kann (wie InvoiceNinja). */
|
|
export function isInvoiceReady(c) {
|
|
return Boolean(c && c.name && c.email && c.street && c.city && c.postalCode)
|
|
}
|
|
|
|
export function customerStage(c) {
|
|
const s = (c?.customerStatus || '').toLowerCase()
|
|
if (s === 'lead') return 'lead'
|
|
if (s === 'lost') return 'lost'
|
|
return 'customer' // Bestandsdaten ohne Status gelten als fester Kunde
|
|
}
|
|
|
|
function ninjaPayload(c) {
|
|
return {
|
|
name: c.companyName || c.name,
|
|
email: c.email || '',
|
|
phone: c.phone || '',
|
|
street: c.street || '',
|
|
city: c.city || '',
|
|
postalCode: c.postalCode || '',
|
|
country: c.country || '',
|
|
vatNumber: c.vatNumber || '',
|
|
contactName: c.contactName || '',
|
|
}
|
|
}
|
|
|
|
/** Erstellt/aktualisiert den InvoiceNinja-Client und speichert die ID am Kunden. */
|
|
export async function syncCustomerToNinja(customer) {
|
|
if (customer.invoiceNinjaClientId) {
|
|
await integrationsApi.updateClient(customer.invoiceNinjaClientId, ninjaPayload(customer))
|
|
return customer.invoiceNinjaClientId
|
|
}
|
|
const r = await integrationsApi.createClient(ninjaPayload(customer))
|
|
const id = r.client?.id
|
|
if (id) {
|
|
await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, {
|
|
invoiceNinjaClientId: id,
|
|
})
|
|
}
|
|
return id
|
|
}
|
|
|
|
async function nextWoid() {
|
|
try {
|
|
const res = await databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [
|
|
Query.orderDesc('$createdAt'),
|
|
Query.limit(200),
|
|
])
|
|
const nums = res.documents.map((d) => parseInt(d.woid)).filter((n) => !isNaN(n) && n > 0)
|
|
return String((nums.length ? Math.max(...nums) : 9999) + 1)
|
|
} catch {
|
|
return String(10000)
|
|
}
|
|
}
|
|
|
|
/** Auto-Akquise-Ticket fuer einen potenziellen Kunden. */
|
|
export async function createAcquisitionTicket(customer, user) {
|
|
const woid = await nextWoid()
|
|
const data = {
|
|
topic: `Akquise: ${customer.name}`,
|
|
status: 'Open',
|
|
priority: 2,
|
|
woid,
|
|
type: 'Akquise',
|
|
systemType: 'n/a',
|
|
serviceType: 'Remote',
|
|
customerId: customer.$id,
|
|
customerName: customer.name,
|
|
customerLocation: customer.location || '',
|
|
requestedBy: customer.email || '',
|
|
assignedTo: user?.$id || '',
|
|
details:
|
|
`Automatisch erstelltes Akquise-Ticket fuer den potenziellen Kunden "${customer.name}".\n` +
|
|
`Kontakt: ${customer.email || '-'}\n\n` +
|
|
`Worksheets dokumentieren den Kontaktverlauf (sichtbar in den WSIDs).\n` +
|
|
`Ergebnis im Worksheet auf "Zugesagt" -> Lead wird automatisch fester Kunde. "Abgesagt" -> Ticket geschlossen.`,
|
|
createdAt: new Date().toISOString(),
|
|
}
|
|
return databases.createDocument(DATABASE_ID, COLLECTIONS.WORKORDERS, ID.unique(), data)
|
|
}
|
|
|
|
/** Legt einen Lead an (nur Unternehmen + E-Mail) und erstellt das Akquise-Ticket. */
|
|
export async function createLead({ company, email }, user) {
|
|
const now = new Date().toISOString()
|
|
const customer = await databases.createDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, ID.unique(), {
|
|
name: company,
|
|
companyName: company,
|
|
email: email || '',
|
|
customerStatus: 'lead',
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
let ticket = null
|
|
try {
|
|
ticket = await createAcquisitionTicket(customer, user)
|
|
} catch {
|
|
/* Ticket ist optional - Lead bleibt bestehen */
|
|
}
|
|
return { customer, ticket }
|
|
}
|
|
|
|
/** Macht aus einem Lead einen festen Kunden (mit vollstaendigen Daten) und legt den InvoiceNinja-Client an. */
|
|
export async function upgradeToCustomer(customer, fullData) {
|
|
const updated = await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, {
|
|
...fullData,
|
|
customerStatus: 'customer',
|
|
updatedAt: new Date().toISOString(),
|
|
})
|
|
let clientId = updated.invoiceNinjaClientId
|
|
try {
|
|
clientId = await syncCustomerToNinja(updated)
|
|
} catch (e) {
|
|
return { customer: updated, clientId: null, syncError: e.message }
|
|
}
|
|
return { customer: { ...updated, invoiceNinjaClientId: clientId }, clientId }
|
|
}
|