import { config } from '../config.js' import { berlinDateTime } from './berlinTime.js' import { listDocuments, Query } from './appwriteAdmin.js' import { sendStaffMail } from './mailer.js' import { createPushTimeToken } from './pushTimeToken.js' /** * Push-Zeitmail (Vorlage T15): nach einem neuen Git-Push-Arbeitsblatt bekommt die pushende Person * eine Mail mit Ticket- und Push-Infos und Schnell-Knoepfen zum Eintragen der Zeit (/zeit/). */ const QUICK_MINUTES = [15, 30, 45, 60, 90, 120, 180] const MAX_COMMITS = 10 const DESCRIPTION_MAX = 300 // Deutsche Bezeichnungen wie in WOMS (lib/ticketStatus.js) const STATUS_LABELS = { Open: 'Offen', Assigned: 'Eingeplant', Occupied: 'In Arbeit', Awaiting: 'Wartet', 'In Test': 'Prüfung', Closed: 'Erledigt', Cancelled: 'Abgebrochen', } const norm = (value) => String(value || '').trim().toLowerCase() /** * Gitea-Pusher einem Mitarbeiter zuordnen - dieselbe Regel wie canEditWorksheetTimes in WOMS: * Gitea-Name gegen Kuerzel oder Anzeigenamen, ohne Gross-/Kleinschreibung. Nur mit E-Mail-Adresse. */ export function findEmployeeForPusher(pusher, employees) { const names = [pusher?.username, pusher?.login, pusher?.full_name].map(norm).filter(Boolean) if (!names.length) return null const sorted = [...(employees || [])].sort((a, b) => String(a.$createdAt || '').localeCompare(String(b.$createdAt || ''))) return ( sorted.find( (emp) => String(emp.email || '').trim() && names.some((n) => n === norm(emp.shortcode) || n === norm(emp.displayName)) ) || null ) } function commitLines(commits) { const list = Array.isArray(commits) ? commits : [] const lines = list.slice(0, MAX_COMMITS).map((commit) => { const sha = String(commit.id || commit.sha || '').slice(0, 7) const title = String(commit.message || '').trim().split('\n')[0] return `${sha} ${title}`.trim() }) if (list.length > MAX_COMMITS) lines.push(`… und ${list.length - MAX_COMMITS} weitere`) return lines.join('\n') || '(keine Commit-Details)' } function shorten(text, max) { const value = String(text || '').trim() return value.length > max ? `${value.slice(0, max)}…` : value } /** Werte fuer Vorlage T15 */ export function buildPushTimeMail({ employee, ticket, worksheet, repoFullName, branch, commits, pushedAt = new Date() }) { const token = createPushTimeToken({ worksheetId: worksheet.$id, employeeId: employee.$id }) const base = `${config.publicBaseUrl}/zeit/${token}` const { date, time } = berlinDateTime(pushedAt) const woid = String(ticket?.woid || worksheet?.woid || '') const links = Object.fromEntries(QUICK_MINUTES.map((m) => [`link_${m}`, `${base}?min=${m}`])) return { template: 'T15', values: { vorname: String(employee.displayName || '').trim().split(/\s+/)[0] || 'du', woid, kunde: ticket?.customerName || 'ohne Kunde', ticket_titel: ticket?.topic || '', ticket_beschreibung: shorten(ticket?.details, DESCRIPTION_MAX), ticket_status: STATUS_LABELS[ticket?.status] || ticket?.status || '', repo: repoFullName, branch, push_zeit: `${date} ${time.slice(0, 2)}:${time.slice(2)}`, anzahl_commits: Array.isArray(commits) ? commits.length : 0, commits: commitLines(commits), ...links, link_andere: base, ticket_link: `${config.womsBaseUrl}/tickets?woid=${encodeURIComponent(woid)}`, }, } } /** Mail verschicken; wirft nie (Webhook darf daran nicht scheitern). */ export async function sendPushTimeMail({ ticket, worksheet, repoFullName, branch, commits, pusher, pushedAt }) { try { const employees = await listDocuments(config.collections.employees, [Query.limit(100)]) const employee = findEmployeeForPusher(pusher, employees) if (!employee) { const name = pusher?.username || pusher?.login || pusher?.full_name || '?' console.warn(`[pushTimeMail] Pusher "${name}" keinem Mitarbeiter mit E-Mail zugeordnet – keine Zeitmail`) return { sent: false, reason: 'no_employee' } } const mail = buildPushTimeMail({ employee, ticket, worksheet, repoFullName, branch, commits, pushedAt }) await sendStaffMail(employee.email, mail) return { sent: true } } catch (err) { console.error('[pushTimeMail] Versand fehlgeschlagen:', err.message) return { sent: false, reason: err.message } } }