// ───────────────────────────────────────────────────────────── // Übergabe- / Rückgabe-Workflows // Guided step-by-step process for handing equipment over to a tenant // (Übergabe) and taking it back (Rückgabe), with checklists, deposit / // fee / ID confirmation, and a damage/missing report that writes straight // into the Lager (equipment.repairQty / accessory.defectQty). // // Data model — rf-workflows: { [rentalId]: { // handover: { checks:{[key]:bool}, deposit:bool, fee:bool, id:bool, done:bool, doneAt:iso }, // ret: { checks:{[key]:bool}, depositBack:bool, done:bool, doneAt:iso }, // reports: [{ id, itemType:'equipment'|'accessory', itemId, name, issue:'defect'|'missing', qty, note, at }] // }} // ───────────────────────────────────────────────────────────── const { useState: useStateWF, useEffect: useEffectWF } = React; // Blank workflow record function wfBlank() { return { handover: { checks: {}, deposit: false, fee: false, id: false, done: false, doneAt: null }, ret: { checks: {}, depositBack: false, done: false, doneAt: null }, reports: [] }; } function wfGet(workflows, rentalId) { const w = workflows && workflows[rentalId]; if (!w) return wfBlank(); return { ...wfBlank(), ...w, handover: { ...wfBlank().handover, ...(w.handover || {}) }, ret: { ...wfBlank().ret, ...(w.ret || {}) }, reports: w.reports || [] }; } // Which action is due for a rental? 'handover' | 'return' | null // handover: not yet handed over and pickup within ±2 days (or already past-due). // return: handed over, not yet returned, and end within ±2 days (or past-due). function wfDue(rental, workflows, today) { if (!rental || rental.status === 'abgeschlossen') return null; const w = wfGet(workflows, rental.id); const near = (iso, back = 2, fwd = 2) => { if (!iso) return false; const d = Math.round((fromISO(iso) - fromISO(today)) / 86400000); return d >= -60 && d <= fwd; // due once within window, stays due if overdue }; if (!w.handover.done && near(rental.start)) return 'handover'; if (w.handover.done && !w.ret.done && near(rental.end)) return 'return'; return null; } // Build the equipment + accessory line items a workflow checklist covers. function wfLines(rental, accessories = []) { const eq = (window.getRentalItems ? window.getRentalItems(rental) : []).map(it => ({ key: 'e:' + it.equipmentId, type: 'equipment', id: it.equipmentId, name: it.equipmentName || it.name, qty: Math.max(1, Number(it.quantity) || 1), })); const acc = (rental.accessoryItems || []).map(a => ({ key: 'a:' + a.accessoryId, type: 'accessory', id: a.accessoryId, name: a.name, qty: Math.max(1, Number(a.quantity) || 1), cat: (accessories.find(x => x.id === a.accessoryId) || {}).cat, })); return { eq, acc, all: [...eq, ...acc] }; } // Overall workflow progress label function wfStatusMeta(w, t) { if (w.ret.done) return { label: 'Abgeschlossen', color: t.textSec, tint: t.chip }; if (w.handover.done) return { label: 'Läuft · Rückgabe offen', color: t.accent, tint: t.accentSoft }; return { label: 'Übergabe offen', color: t.green, tint: t.greenSoft }; } // ─── The step-by-step runner sheet ─────────────────────────── function WorkflowSheet({ open, mode, rental, workflows, setWorkflows, equipment, setEquipment, accessories, setAccessories, setRentals, onClose, toast }) { const t = useTheme(); const [step, setStep] = useStateWF(0); const [reportFor, setReportFor] = useStateWF(null); // line being reported useEffectWF(() => { if (open) { setStep(0); setReportFor(null); } }, [open, rental && rental.id, mode]); if (!rental) return null; const w = wfGet(workflows, rental.id); const lines = wfLines(rental, accessories); const patch = (fn) => setWorkflows(prev => { const cur = wfGet(prev, rental.id); const next = fn(JSON.parse(JSON.stringify(cur))); return { ...prev, [rental.id]: next }; }); const isReturn = mode === 'return'; const branch = isReturn ? w.ret : w.handover; const setCheck = (key, val) => patch(c => { const b = isReturn ? c.ret : c.handover; b.checks[key] = val; return c; }); const allChecked = lines.all.length > 0 && lines.all.every(l => branch.checks[l.key]); const toggleAll = (val) => patch(c => { const b = isReturn ? c.ret : c.handover; lines.all.forEach(l => { b.checks[l.key] = val; }); return c; }); // Damage / missing report — writes into Lager const submitReport = (rep) => { patch(c => { c.reports = [...(c.reports || []), { ...rep, id: 'rep-' + Date.now().toString(36), at: todayISO() }]; return c; }); if (rep.itemType === 'equipment' && setEquipment) { setEquipment(prev => prev.map(e => e.id === rep.itemId ? { ...e, repairQty: Math.max(0, (Number(e.repairQty) || 0) + (Number(rep.qty) || 1)), maint: [...(e.maint || []), { id: 'm-' + Date.now().toString(36), type: rep.issue === 'missing' ? 'defect' : 'repair', title: (rep.issue === 'missing' ? 'Fehlt nach Rückgabe' : 'Schaden nach Rückgabe') + ' · ' + rental.tenantName, date: todayISO(), done: false, note: rep.note || '' }] } : e)); } if (rep.itemType === 'accessory' && setAccessories) { setAccessories(prev => prev.map(a => a.id === rep.itemId ? { ...a, defectQty: Math.max(0, (Number(a.defectQty) || 0) + (Number(rep.qty) || 1)), note: [a.note, (rep.issue === 'missing' ? 'Fehlt' : 'Schaden') + ' (' + rental.tenantName + '): ' + (rep.note || '')].filter(Boolean).join(' · ') } : a)); } if (toast) toast(rep.issue === 'missing' ? 'Als fehlend im Lager vermerkt' : 'Schaden im Lager vermerkt'); setReportFor(null); }; // Steps definition const HANDOVER_STEPS = ['Equipment einpacken', 'Zubehör einpacken', 'Kaution & Gebühr', 'Ausweis', 'Abschluss']; const RETURN_STEPS = ['Equipment zurück', 'Zubehör zurück', 'Kaution zurück', 'Abschluss']; const steps = isReturn ? RETURN_STEPS : HANDOVER_STEPS; const finish = () => { patch(c => { const b = isReturn ? c.ret : c.handover; b.done = true; b.doneAt = todayISO(); return c; }); if (setRentals) { setRentals(prev => prev.map(r => r.id === rental.id ? { ...r, status: isReturn ? 'abgeschlossen' : 'aktiv', depositStatus: isReturn ? 'zurueckgezahlt' : 'erhalten', paymentStatus: isReturn ? r.paymentStatus : 'bezahlt' } : r)); } if (toast) toast(isReturn ? 'Rückgabe abgeschlossen' : 'Übergabe abgeschlossen'); onClose(); }; // ── Checklist renderer ── const Checklist = ({ items, emptyLabel, withReport }) => (