Rechnungsanalyse: Termin landet im Akquise-Ticket und damit im Planboard

Der im Gespraech vereinbarte Termin wird mit einem Klick am Akquise-Ticket
eingetragen (startDate/startTime/endTime/assignedTo/assignedName) - genau die
Felder, die das WOMS-Planboard liest. Kein zweiter Datensatz, damit Termin und
Ticket nicht auseinanderlaufen koennen.

- server/services/appointments.js: Ticket des Kunden finden, Termin schreiben,
  belegte Zeiten lesen; Umrechnung ISO <-> "dd.mm.yyyy" und "hh:mm" <-> "hhmm"
- Routen GET /api/portal-admin/appointments und
  POST /api/portal-admin/customers/:id/appointment
- Bereits vergebene Zeiten sind in der Terminauswahl gesperrt ("belegt"), der
  eigene Termin bleibt waehlbar; steht am Ticket schon einer, ist er beim
  Oeffnen vorausgewaehlt
- Zeiten, die jemand von Hand in WOMS gesetzt hat, tauchen mit auf

Sicherung gegen Datenverlust: bevorzugt wird das Akquise-Ticket. Gibt es keins,
kommt nur ein anderes offenes Ticket OHNE Datum in Frage - bei Webpage- und
Projekttickets bedeutet startDate den Projektstart, den wuerden wir sonst still
ueberschreiben. Die Rueckmeldung nennt immer Nummer und Art des Tickets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-08 22:09:51 +00:00
parent c4a6c90306
commit ac264dc9ba
5 changed files with 345 additions and 6 deletions

View File

@@ -13,6 +13,7 @@ import {
Query,
ID,
} from '../services/appwriteAdmin.js'
import { listAppointments, saveAppointment } from '../services/appointments.js'
import { getCustomerActivity } from '../services/customerActivity.js'
import { listInvoicesForClient } from '../services/invoiceNinja.js'
import { sanitizeMessage } from './chat.js'
@@ -185,6 +186,36 @@ router.get('/customers', async (req, res) => {
}
})
// ----------------------------------------------------------------- Termine --
// Termine haengen am Akquise-Ticket (workorders) und werden vom WOMS-Planboard
// gelesen. Siehe server/services/appointments.js.
router.get('/appointments', async (req, res) => {
try {
const appointments = await listAppointments({
from: String(req.query.from || ''),
to: String(req.query.to || ''),
})
return res.json({ appointments })
} catch (err) {
return res.status(err.status || 500).json({ error: err.message || 'Termine konnten nicht geladen werden' })
}
})
router.post('/customers/:customerId/appointment', async (req, res) => {
const { date, time, employeeEmail } = req.body || {}
try {
const appointment = await saveAppointment({
customerId: req.params.customerId,
employeeEmail,
date,
time,
})
return res.json({ success: true, appointment })
} catch (err) {
return res.status(err.status || 500).json({ error: err.message || 'Termin konnte nicht gespeichert werden' })
}
})
// --------------------------------------------- Einzelner Kunde (Stammdaten) --
// Fuer die Rechnungsanalyse (kalkulation.html), die den Lead vorbefuellt.
router.get('/customers/:customerId', async (req, res) => {