Projektdetails und Fortschritt kundenverständlich

Der Server bereitet Commits, Tickets und Arbeitseinträge für Kunden auf
(server/services/customerProjectView.js), die Rohdaten bleiben intern:
- keine Co-Authored-By-/Signed-off-by-Zeilen und KI-Hinweise, keine
  Commit-Kürzel, Kontonamen, Git-Links oder Branch
- technische Commits (Preview-Config, Merges) ausgeblendet, bekannte
  Automatik-Texte übersetzt, "fix"/"upload" -> "Website aktualisiert"
- Ticketstatus und Arbeitsarten auf Deutsch (Open -> Eingegangen,
  Remote -> Online erledigt, ...), Fortschritt in 4 Schritten
- Akquise-Tickets auch in den Projektdetails verborgen
- /api/projects ohne Gitea-URL, Repo- und Vorlagennamen

Darstellung: Auftragskarte mit Status und Fortschrittsleiste, Zeitleiste
"Letzte Änderungen" nach Tagen, Arbeiten mit Farbe/Symbol je Art und
Arbeitszeit; der Tab "Fortschritt" nutzt dieselben Bausteine. In der
App-Ansicht öffnen sich die Projektdetails als eigene Seite mit Zurück
zu "Projekte".

Tests: npm test (node --test, 22 Fälle).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-26 18:46:22 +00:00
1 parent e54f97144c
commit ba9916cfe6
9 files changed
+932 -138

No files matched your search

+7 -1
View File
@@ -3,6 +3,7 @@ import { config } from '../config.js'
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
import { getDocument, listDocuments, Query } from '../services/appwriteAdmin.js'
import { getCustomerActivity } from '../services/customerActivity.js'
import { customerOrder, customerWorkEntries } from '../services/customerProjectView.js'
import { fetchInvoicePdf, getInvoiceRaw, listInvoicesForClient } from '../services/invoiceNinja.js'
const router = Router()
@@ -69,7 +70,12 @@ router.get('/activity', requireSession, async (req, res) => {
// Interne Akquise-Tickets gehoeren nicht in die Kundenansicht
excludeTypes: ['Akquise'],
})
return res.json({ tickets })
// Status auf Deutsch, Arbeiten ohne Git-Technik (customerProjectView.js)
const orders = tickets
.map((t) => ({ order: customerOrder(t), createdAt: t.createdAt, entries: customerWorkEntries(t.worksheets) }))
.filter((t) => t.order)
.map(({ order, createdAt, entries }) => ({ ...order, createdAt, entries }))
return res.json({ tickets: orders })
} catch (err) {
return res.status(500).json({ error: err.message || 'Aktivität konnte nicht geladen werden' })
}
+15 -29
View File
@@ -3,6 +3,7 @@ import { config } from '../config.js'
import { getDocument, listDocuments, Query } from '../services/appwriteAdmin.js'
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
import { getProjectGitInsights, getProjectFileOverview } from '../services/projectInsights.js'
import { customerChanges, customerOrder, customerWorkEntries } from '../services/customerProjectView.js'
const router = Router()
@@ -40,9 +41,6 @@ router.get('/', requireSession, async (req, res) => {
liveDomain: p.liveDomain || '',
status: p.status || '',
provisioningStatus: p.provisioningStatus || '',
templateName: p.templateName || '',
giteaRepoUrl: p.giteaRepoUrl || '',
repoFullName: p.repoFullName || '',
updatedAt: p.updatedAt || p.$updatedAt || '',
}))
@@ -52,11 +50,11 @@ router.get('/', requireSession, async (req, res) => {
}
})
/** Letzte Git-Pushes + Projektgroesse. */
/** Letzte Aenderungen in Kundensprache (ohne Commit-Kuerzel, Kontonamen, KI-Hinweise). */
router.get('/:id/git', requireSession, loadOwnProject, async (req, res) => {
try {
const { repo, commits } = await getProjectGitInsights(req.project)
return res.json({ repo, commits })
const { commits } = await getProjectGitInsights(req.project)
return res.json({ changes: customerChanges(commits) })
} catch (err) {
return res.status(500).json({ error: err.message || 'Git-Daten konnten nicht geladen werden' })
}
@@ -73,40 +71,28 @@ router.get('/:id/files', requireSession, loadOwnProject, async (req, res) => {
}
})
/** Ticket-Arbeiten am Projekt (inkl. Git-Push-Worksheets, "Zeit offen" markiert). */
/**
* Auftrag zum Projekt (Status auf Deutsch) und dokumentierte Arbeiten. Git-Pushes ohne
* Arbeitszeit stehen schon unter "Letzte Änderungen"; interne Tickets (Akquise) bleiben verborgen.
*/
router.get('/:id/work', requireSession, loadOwnProject, async (req, res) => {
try {
const project = req.project
if (!project.ticketId) {
return res.json({ ticket: null, worksheets: [] })
const ticket = project.ticketId
? await getDocument(config.collections.workorders, project.ticketId).catch(() => null)
: null
const order = customerOrder(ticket)
if (!order) {
return res.json({ order: null, entries: [] })
}
const ticket = await getDocument(config.collections.workorders, project.ticketId).catch(() => null)
const worksheets = await listDocuments(config.collections.worksheets, [
Query.equal('workorderId', project.ticketId),
Query.orderDesc('$createdAt'),
Query.limit(50),
]).catch(() => [])
const sanitized = worksheets
// GIT-Eintraege einschliessen, sonstige reine Kommentare ausblenden
.filter((ws) => ws.serviceType === 'GIT' || !ws.isComment)
.map((ws) => ({
wsid: ws.wsid || '',
date: ws.endDate || ws.startDate || '',
time: ws.endTime || ws.startTime || '',
employeeName: ws.employeeName || ws.employeeShort || '',
serviceType: ws.serviceType || '',
details: ws.details || '',
totalTime: Number(ws.totalTime || 0),
isGit: ws.serviceType === 'GIT',
pending: ws.serviceType === 'GIT' && !ws.startTime,
}))
return res.json({
ticket: ticket ? { woid: ticket.woid || '', topic: ticket.topic || '', status: ticket.status || '' } : null,
worksheets: sanitized,
})
return res.json({ order, entries: customerWorkEntries(worksheets, { includeUpdates: false }) })
} catch (err) {
return res.status(500).json({ error: err.message || 'Arbeiten konnten nicht geladen werden' })
}