Lead zum festen Kunden nur mit Kundenart
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
463aaf1066
commit
58bb956645
5 files changed
+62
-6
No files matched your search
@@ -193,7 +193,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
<Field
|
||||
label={isAcquisition ? 'Akquise-Ergebnis' : 'Neuer Status'}
|
||||
htmlFor="worksheet-new-status"
|
||||
hint={isAcquisition ? '"Zugesagt" macht den Lead automatisch zum festen Kunden.' : undefined}
|
||||
hint={isAcquisition ? '„Zugesagt“ macht den Lead zum festen Kunden, sobald seine Kundenart festgelegt ist.' : undefined}
|
||||
>
|
||||
<select
|
||||
id="worksheet-new-status"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/* Folgen eines neuen Arbeitsblatts.
|
||||
Kennt kein Appwrite: die Seite reicht die Schreibzugriffe herein. */
|
||||
|
||||
import { kundenartGesetzt } from './kundenFelder'
|
||||
|
||||
export const KUNDENART_ZUGESAGT_HINWEIS =
|
||||
'Das Arbeitsblatt ist gespeichert, der Lead wurde aber noch nicht zum festen Kunden gemacht: Die Kundenart fehlt. Lege sie beim Kunden unter „Rechnungsdaten“ fest und wähle dann „Zum festen Kunden machen“.'
|
||||
|
||||
// Akquise-Automatik: das Ergebnis im Arbeitsblatt steuert den Kundenstatus.
|
||||
// "customer" = Zugesagt, "lost" = Abgesagt, sonst keine Aenderung.
|
||||
export function acquisitionOutcome(ticket, newStatus) {
|
||||
@@ -10,10 +15,21 @@ export function acquisitionOutcome(ticket, newStatus) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Ist die Kundenart des Kunden gesetzt? Fail-closed: nicht ladbar, null oder ohne getCustomer heisst "nicht gesetzt".
|
||||
async function kundenartDesKunden(getCustomer, customerId) {
|
||||
try {
|
||||
const doc = await getCustomer?.(customerId)
|
||||
return kundenartGesetzt(doc?.kundenart)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* 1. Arbeitsblatt anlegen. 2. Bei Erfolg den Ticket-Status nachziehen, wenn er abweicht.
|
||||
3. Bei Akquise-Tickets den Kundenstatus setzen (Fehler werden verschluckt).
|
||||
Rueckgabe ist das Ergebnis von createWorksheet. */
|
||||
export async function createWorksheetFlow({ ticket, worksheetData, user, createWorksheet, updateTicket, updateCustomer, now = () => new Date() }) {
|
||||
3. Bei Akquise-Tickets den Kundenstatus setzen (Fehler werden verschluckt). "Zugesagt" stuft den Lead nur hoch,
|
||||
wenn seine Kundenart gesetzt ist; sonst bleiben Blatt und Ticketstatus, der Lead bleibt Lead und das Ergebnis traegt
|
||||
einen Hinweis. Rueckgabe ist das Ergebnis von createWorksheet (ggf. mit hinweis). */
|
||||
export async function createWorksheetFlow({ ticket, worksheetData, user, createWorksheet, updateTicket, updateCustomer, getCustomer, now = () => new Date() }) {
|
||||
const result = await createWorksheet(worksheetData, user)
|
||||
if (!result?.success) return result
|
||||
|
||||
@@ -23,6 +39,9 @@ export async function createWorksheetFlow({ ticket, worksheetData, user, createW
|
||||
}
|
||||
|
||||
const customerStatus = acquisitionOutcome(ticket, newStatus)
|
||||
if (customerStatus === 'customer' && !(await kundenartDesKunden(getCustomer, ticket.customerId))) {
|
||||
return { ...result, hinweis: KUNDENART_ZUGESAGT_HINWEIS }
|
||||
}
|
||||
if (customerStatus) {
|
||||
try {
|
||||
await updateCustomer(ticket.customerId, { customerStatus, updatedAt: now().toISOString() })
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
|
||||
import { customerTabs, resolveCustomerTab } from '../lib/customerTabs'
|
||||
import { isInvoiceReady, setCustomerArchived, upgradeToCustomer, markLost, reopenAsLead } from '../lib/leads'
|
||||
import { lastRejection } from '../lib/leadDuplicates'
|
||||
import { KUNDENART_OPTIONEN, KUNDENART_HINWEIS, kundenartAenderung } from '../lib/kundenFelder'
|
||||
import { KUNDENART_OPTIONEN, KUNDENART_HINWEIS, KUNDENART_HOCHSTUFEN_HINWEIS, kundenartAenderung, kundenartPruefen } from '../lib/kundenFelder'
|
||||
import { customerStage, isArchived, customerBadges, customerEyebrow, customerTitle } from '../lib/customerList'
|
||||
import { ticketPath, ticketRef } from '../lib/ticketRoutes'
|
||||
import { ticketNumber } from '../lib/ticketView'
|
||||
@@ -196,6 +196,7 @@ function OverviewTab({ customer, stage, onSaved }) {
|
||||
}
|
||||
|
||||
const upgrade = async () => {
|
||||
if (kundenartPruefen(kundenart)) { setMsg({ tone: 'warn', text: KUNDENART_HOCHSTUFEN_HINWEIS }); return }
|
||||
if (!ready) { setMsg({ tone: 'warn', text: 'Bitte zuerst alle Pflichtfelder (Unternehmen, E-Mail, Strasse, PLZ, Stadt) ausfuellen.' }); return }
|
||||
setBusy(true); setMsg(null)
|
||||
try {
|
||||
|
||||
@@ -90,6 +90,8 @@ function TicketView({ ticket, updateWorkorder, tab }) {
|
||||
const [showCreateWorksheet, setShowCreateWorksheet] = useState(false)
|
||||
const [editTimesWorksheet, setEditTimesWorksheet] = useState(null)
|
||||
const [ownSupportWorksheet, setOwnSupportWorksheet] = useState(null)
|
||||
// Hinweis des Arbeitsblatt-Flusses (z. B. "Zugesagt" ohne Kundenart); jedes neue Blatt setzt ihn neu
|
||||
const [hinweis, setHinweis] = useState('')
|
||||
// Werte, die gerade gespeichert werden: das Feld zeigt den neuen Wert und ist gesperrt, bei einem Fehler springt es zurück
|
||||
const [pending, setPending] = useState({})
|
||||
|
||||
@@ -140,7 +142,9 @@ function TicketView({ ticket, updateWorkorder, tab }) {
|
||||
createWorksheet,
|
||||
updateTicket: updateWorkorder,
|
||||
updateCustomer: (id, data) => databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, id, data),
|
||||
getCustomer: (id) => databases.getDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, id),
|
||||
})
|
||||
setHinweis(result?.hinweis || '')
|
||||
// Das neue Blatt soll sichtbar sein
|
||||
if (result?.success && tab !== 'arbeitsblaetter') goToTab('arbeitsblaetter')
|
||||
return result
|
||||
@@ -238,6 +242,8 @@ function TicketView({ ticket, updateWorkorder, tab }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hinweis && <p className="msg warn mb-2" role="alert">{hinweis}</p>}
|
||||
|
||||
<Tabs
|
||||
tabs={[
|
||||
{ id: 'details', label: 'Details' },
|
||||
|
||||
@@ -2,7 +2,7 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { loadModule } from './load.mjs'
|
||||
|
||||
const { createWorksheetFlow, acquisitionOutcome } = await loadModule('src/lib/worksheetFlow.js')
|
||||
const { createWorksheetFlow, acquisitionOutcome, KUNDENART_ZUGESAGT_HINWEIS } = await loadModule('src/lib/worksheetFlow.js')
|
||||
async function run(ticket, newStatus, over = {}) {
|
||||
const calls = { ticket: [], customer: [] }
|
||||
const result = await createWorksheetFlow({
|
||||
@@ -10,6 +10,7 @@ async function run(ticket, newStatus, over = {}) {
|
||||
createWorksheet: async () => over.result ?? { success: true },
|
||||
updateTicket: async (id, data) => { calls.ticket.push([id, data]) },
|
||||
updateCustomer: async (id, data) => { calls.customer.push([id, data]); if (over.customerFails) throw new Error('weg') },
|
||||
getCustomer: 'getCustomer' in over ? over.getCustomer : async () => ({ kundenart: 'unternehmen' }),
|
||||
now: () => new Date('2026-10-05T10:00:00Z'),
|
||||
})
|
||||
return { result, calls }
|
||||
@@ -38,3 +39,32 @@ test('Fehlschlag und Kundenfehler', async () => {
|
||||
assert.equal((await run(akq, 'Zugesagt', { customerFails: true })).result.success, true)
|
||||
assert.equal(acquisitionOutcome({ type: 'Akquise', customerId: 'c' }, 'Zugesagt'), 'customer')
|
||||
})
|
||||
test('Zugesagt mit gesetzter Kundenart (unternehmen, privat): Kunde fest, kein Hinweis', async () => {
|
||||
for (const kundenart of ['unternehmen', 'privat']) {
|
||||
const { calls, result } = await run(akq, 'Zugesagt', { getCustomer: async () => ({ kundenart }) })
|
||||
assert.deepEqual(calls.customer, [['c1', { customerStatus: 'customer', updatedAt: '2026-10-05T10:00:00.000Z' }]], kundenart)
|
||||
assert.equal(result.hinweis, undefined)
|
||||
}
|
||||
})
|
||||
test('Zugesagt ohne gültige Kundenart: Blatt und Ticketstatus bleiben, der Lead bleibt Lead, Hinweis', async () => {
|
||||
for (const kundenart of [undefined, '', 'Unternehmen', 'firma', ' privat']) {
|
||||
const { calls, result } = await run(akq, 'Zugesagt', { getCustomer: async () => ({ kundenart }) })
|
||||
assert.deepEqual(calls, { ticket: [['t1', { status: 'Zugesagt' }]], customer: [] }, String(kundenart))
|
||||
assert.equal(result.success, true)
|
||||
assert.equal(result.hinweis, KUNDENART_ZUGESAGT_HINWEIS)
|
||||
}
|
||||
})
|
||||
test('Kunde nicht ladbar, null oder ohne getCustomer: wie ohne Kundenart', async () => {
|
||||
for (const getCustomer of [async () => { throw new Error('weg') }, async () => null, undefined]) {
|
||||
const { calls, result } = await run(akq, 'Zugesagt', { getCustomer })
|
||||
assert.deepEqual(calls.customer, [])
|
||||
assert.equal(result.hinweis, KUNDENART_ZUGESAGT_HINWEIS)
|
||||
}
|
||||
})
|
||||
test('Abgesagt und andere Ergebnisse brauchen keine Kundenart und laden den Kunden nicht', async () => {
|
||||
let geladen = 0
|
||||
const getCustomer = async () => { geladen += 1; return {} }
|
||||
assert.deepEqual((await run(akq, 'Abgesagt', { getCustomer })).calls.customer, [['c1', { customerStatus: 'lost', updatedAt: '2026-10-05T10:00:00.000Z' }]])
|
||||
await run(akq, 'In Kontakt', { getCustomer })
|
||||
assert.equal(geladen, 0)
|
||||
})
|
||||
Reference in new issue
Block a user