/* Powermieter — Contract Management data backbone. Vertragsmodelle, Status, Vertragsarten je Rolle, Dokumente, Einheiten-Verträge, Go-Live-Logik. Alles auf window exportiert. */ /* ---------------- Vertragsmodelle (Objekt-Ebene) ---------------- */ const VERTRAGSMODELLE = { mieterstrom: { id: 'mieterstrom', label: 'Mieterstrom', kurz: 'Mieterstrom', legal: '§ 42a EnWG', desc: 'Klassisches Mieterstrommodell mit eigenem Stromliefervertrag je Bewohner. Mieterstromzuschlag förderfähig.', zuschlag: true, tone: 'accent', }, ggv: { id: 'ggv', label: 'Gemeinschaftliche Gebäudeversorgung', kurz: 'gGV', legal: '§ 42b EnWG', desc: 'Teilnahme- & Verteilvereinbarung statt Stromliefervertrag. Kein Mieterstromzuschlag, schlankere Strecke.', zuschlag: false, tone: 'positive', }, hybrid: { id: 'hybrid', label: 'Hybridmodell', kurz: 'Hybrid', legal: '§ 42a + § 42b EnWG', desc: 'Mieterstrom und gGV parallel je Einheit — das System wählt die passende Vertragsstrecke pro Teilnehmer.', zuschlag: true, tone: 'accent', }, pre: { id: 'pre', label: 'Nur Vorbereitung', kurz: 'Pre-Onboarding', legal: 'Pre-Onboarding', desc: 'Objekt rechtlich vorbereiten, ohne aktive Belieferung. Verträge als Entwurf, kein Go-Live.', zuschlag: false, tone: 'gray', }, }; const MODELL_ORDER = ['mieterstrom', 'pre']; /* ---------------- Vertragsstatus (Vertrags-Lebenszyklus) ---------------- */ const CSTATUS = { entwurf: { label: 'Entwurf', tone: 'gray', dot: 'var(--text-3)' }, daten_fehlen: { label: 'Daten fehlen', tone: 'warn', dot: 'var(--warn-500)' }, bereit: { label: 'Bereit zum Versand', tone: 'accent', dot: 'var(--accent)' }, versendet: { label: 'Versendet', tone: 'accent', dot: 'var(--accent)' }, angesehen: { label: 'Angesehen', tone: 'accent', dot: 'var(--accent-400)' }, unterzeichnet: { label: 'Unterzeichnet', tone: 'positive', dot: 'var(--positive)' }, gegengezeichnet:{ label: 'Gegengezeichnet', tone: 'positive', dot: 'var(--positive)' }, aktiv: { label: 'Aktiv', tone: 'positive', dot: 'var(--positive-500)' }, abgelehnt: { label: 'Abgelehnt', tone: 'danger', dot: '#E0556B' }, abgelaufen: { label: 'Abgelaufen', tone: 'gray', dot: 'var(--text-3)' }, gekuendigt: { label: 'Gekündigt', tone: 'gray', dot: 'var(--text-3)' }, }; const CSTATUS_ORDER = ['entwurf', 'daten_fehlen', 'bereit', 'versendet', 'angesehen', 'unterzeichnet', 'gegengezeichnet', 'aktiv']; /* how "far" a status is on the signing journey (for readiness/progress) */ const CSTATUS_RANK = { entwurf: 0, daten_fehlen: 0, bereit: 1, versendet: 2, angesehen: 3, unterzeichnet: 4, gegengezeichnet: 5, aktiv: 6, abgelehnt: 0, abgelaufen: 0, gekuendigt: 0 }; /* ---------------- Vertragsarten je Rolle (rechtlich, vollständig) ---------------- */ const ROLE_CONTRACTS = { verwaltung: { label: 'Hausverwaltung / WEG / Eigentümer', icon: 'building', note: 'Rahmenverträge auf Legal-Entity-Ebene — einmal pro Objekt bzw. Eigentümergemeinschaft.', items: [ { name: 'Rahmenvertrag Powermieter Ready / Run', code: 'RV', req: true }, { name: 'Betreibervertrag', code: 'BV', req: true }, { name: 'Dachnutzungs- / Gestattungsvertrag', code: 'DN', req: true }, { name: 'Abrechnungsmandat', code: 'AM', req: true }, { name: 'Vollmacht ggü. Netz- / Messstellenbetreiber', code: 'VM', req: true }, { name: 'WEG-Beschluss / Freigabeprotokoll', code: 'WEG', req: false, hint: 'nur bei WEG' }, { name: 'Datenschutz- & Auftragsverarbeitungsvereinbarung (AVV)', code: 'AVV', req: true }, { name: 'SEPA / Zahlungsdaten', code: 'SEPA', req: false, hint: 'falls relevant' }, ], }, mieter_mieterstrom: { label: 'Mieter / Bewohner · Mieterstrom', icon: 'user', note: 'Echtes Mieterstrommodell nach § 42a EnWG — mit eigenem Stromliefervertrag.', items: [ { name: 'Teilnahmevertrag Mieterstrom', code: 'TN', req: true }, { name: 'Stromliefervertrag', code: 'SL', req: true }, { name: 'Tarifbestätigung', code: 'TB', req: true }, { name: 'Datenschutzfreigabe', code: 'DS', req: true }, { name: 'SEPA-Mandat', code: 'SEPA', req: true }, { name: 'Widerrufsbelehrung / Vertragsinformationen', code: 'WB', req: true }, { name: 'Einwilligung digitale Kommunikation', code: 'EK', req: true }, ], }, mieter_ggv: { label: 'Mieter / Bewohner · gGV', icon: 'users', note: 'Gemeinschaftliche Gebäudeversorgung nach § 42b EnWG — kein Stromliefervertrag, kein Mieterstromzuschlag.', items: [ { name: 'Teilnahmevertrag gGV', code: 'TN', req: true }, { name: 'Teilnahme- & Verteilvereinbarung', code: 'TV', req: true }, { name: 'Tarifbestätigung', code: 'TB', req: true }, { name: 'Datenschutzfreigabe', code: 'DS', req: true }, { name: 'SEPA-Mandat', code: 'SEPA', req: true }, { name: 'Widerrufsbelehrung / Vertragsinformationen', code: 'WB', req: true }, { name: 'Einwilligung digitale Kommunikation', code: 'EK', req: true }, ], }, }; /* which mieter contract set a given object model implies */ function mieterSetFor(model) { return model === 'ggv' ? 'mieter_ggv' : 'mieter_mieterstrom'; } /* ---------------- Dokument-Status ---------------- */ const DOC_STATUS = { fehlt: { label: 'Nicht erzeugt', tone: 'gray' }, entwurf: { label: 'Entwurf', tone: 'gray' }, generiert: { label: 'Generiert', tone: 'accent' }, versendet: { label: 'Versendet', tone: 'accent' }, signiert: { label: 'Signiert', tone: 'positive' }, archiviert: { label: 'Archiviert', tone: 'positive' }, }; /* ---------------- Einheiten-Verträge (Demo-Daten, gespiegelt zu RESIDENTS) ---------------- */ /* dataOk: Stammdaten vollständig · checks erfüllt aus Go-Live-Sicht */ const UNIT_CONTRACTS = [ { whg: 'W01', name: 'Anna Berger', auf: 'A', etage: 'EG', status: 'aktiv', docVer: 'v2.1', signed: '12.05.2026', missing: [] }, { whg: 'W02', name: 'Familie Çelik', auf: 'A', etage: 'EG', status: 'gegengezeichnet', docVer: 'v2.1', signed: '18.05.2026', missing: [] }, { whg: 'W03', name: 'Tobias Wolf', auf: 'A', etage: '1. OG', status: 'versendet', docVer: 'v2.1', signed: null, missing: [] }, { whg: 'W04', name: 'Max Mustermann', auf: 'A', etage: '2. OG', status: 'daten_fehlen', docVer: 'v2.0', signed: null, missing: ['IBAN', 'Zählpunkt'], flag: true }, { whg: 'W05', name: 'Sofia Russo', auf: 'A', etage: '2. OG', status: 'bereit', docVer: 'v2.1', signed: null, missing: [] }, { whg: 'W06', name: 'Leon Fischer', auf: 'A', etage: '3. OG', status: 'entwurf', docVer: '—', signed: null, missing: ['Zustimmung', 'SEPA-Mandat'] }, { whg: 'W07', name: 'Hr. & Fr. Yılmaz',auf: 'B', etage: 'EG', status: 'aktiv', docVer: 'v2.1', signed: '02.05.2026', missing: [] }, { whg: 'W08', name: 'Clara Hoffmann', auf: 'B', etage: 'EG', status: 'unterzeichnet', docVer: 'v2.1', signed: '20.05.2026', missing: [] }, { whg: 'W09', name: 'Jonas Krüger', auf: 'B', etage: '1. OG', status: 'angesehen', docVer: 'v2.1', signed: null, missing: [] }, { whg: 'W10', name: '— leer stehend', auf: 'B', etage: '1. OG', status: 'entwurf', docVer: '—', signed: null, missing: [], vacant: true }, { whg: 'W11', name: 'Mara Schulz', auf: 'B', etage: '2. OG', status: 'daten_fehlen', docVer: 'v2.0', signed: null, missing: ['Tarifbestätigung'], flag: true }, { whg: 'W12', name: 'David Becker', auf: 'B', etage: '3. OG', status: 'bereit', docVer: 'v2.1', signed: null, missing: [] }, ]; /* documents for a single unit, given the object model */ function unitDocs(unit, model) { const set = ROLE_CONTRACTS[mieterSetFor(model)].items; const rank = CSTATUS_RANK[unit.status] || 0; return set.map((it, i) => { let st = 'fehlt'; if (unit.vacant) st = 'fehlt'; else if (rank >= 6) st = 'archiviert'; else if (rank >= 4) st = i < set.length - 1 ? 'signiert' : 'versendet'; else if (rank >= 2) st = 'versendet'; else if (rank >= 1) st = 'generiert'; else if (rank === 0 && unit.status === 'daten_fehlen') st = i < 2 ? 'generiert' : 'fehlt'; else st = i === 0 ? 'entwurf' : 'fehlt'; return { ...it, doc: st, ver: st === 'fehlt' ? '—' : (unit.docVer || 'v2.1'), tpl: `PM-${it.code}-${model.slice(0, 3).toUpperCase()}` }; }); } /* Go-Live gate checklist for a unit. `gateForced`: 'erfuellt' overrides to all-pass for demo. */ function goLiveChecks(unit, model, gateForced) { const rank = CSTATUS_RANK[unit.status] || 0; const base = [ { k: 'teilnehmer', label: 'Teilnehmer aktiv', ok: rank >= 4 }, { k: 'tarif', label: 'Tarif aktiv', ok: rank >= 1 && unit.status !== 'daten_fehlen' }, { k: 'zaehler', label: 'Zähler gemappt', ok: !unit.missing.includes('Zählpunkt') && rank >= 1 }, { k: 'periode', label: 'Abrechnungsperiode gesetzt', ok: rank >= 2 }, { k: 'sepa', label: 'SEPA / Zahlungsweg', ok: !unit.missing.includes('SEPA-Mandat') && !unit.missing.includes('IBAN') }, { k: 'docs', label: 'Dokumente archiviert', ok: rank >= 5 }, { k: 'audit', label: 'Audit-Log vollständig', ok: rank >= 4 }, ]; if (gateForced === 'erfuellt') return base.map(c => ({ ...c, ok: true })); return base; } function readinessPct(unit, model, gateForced) { if (unit.vacant) return 0; const c = goLiveChecks(unit, model, gateForced); return Math.round(c.filter(x => x.ok).length / c.length * 100); } /* Audit-Log / contract_events (Demo) */ function unitEvents(unit) { const ev = [ { t: 'Vertrag als Entwurf angelegt', who: 'System', when: '02.05. 09:14', icon: 'doc' }, ]; const rank = CSTATUS_RANK[unit.status] || 0; if (rank >= 1 || unit.status === 'daten_fehlen') ev.push({ t: `Dokumente generiert (${unit.docVer})`, who: 'System', when: '02.05. 09:15', icon: 'doc' }); if (unit.status === 'daten_fehlen') ev.push({ t: `Daten unvollständig: ${unit.missing.join(', ')}`, who: 'Prüfung', when: '03.05. 11:02', icon: 'alert', warn: true }); if (rank >= 2) ev.push({ t: 'Unterlagen an Bewohner versendet', who: 'L. Wagner (HV)', when: '04.05. 14:20', icon: 'send' }); if (rank >= 3) ev.push({ t: 'Bewohner hat Unterlagen geöffnet', who: unit.name, when: '05.05. 19:46', icon: 'doc' }); if (rank >= 4) ev.push({ t: 'Digital unterzeichnet', who: unit.name, when: (unit.signed || '06.05.') + ' 20:03', icon: 'signature', good: true }); if (rank >= 5) ev.push({ t: 'Gegengezeichnet durch Betreiber', who: 'Powermieter', when: '07.05. 08:30', icon: 'check', good: true }); if (rank >= 6) ev.push({ t: 'Einheit aktiviert — Belieferung gestartet', who: 'System', when: '10.05. 00:00', icon: 'bolt', good: true }); return ev.reverse(); } /* Legal entity / Vertragspartner — gehört auf Legal-Entity-Ebene, nicht auf Building */ const LEGAL_ENTITY = { name: 'Hausverwaltung Stadtquartier GmbH', form: 'GmbH', steuernr: 'DE 12 345 67890', iban: 'DE89 3704 0044 0532 0130 00', ansprech: 'Lena Wagner', rolle: 'Abrechnender Betreiber', }; const OBJEKT = { name: 'Musterstraße 12', plz: '10115 Berlin', we: 12, aufgaenge: 2, zaehlpunkte: 13, messkonzept: 'Summenzählermodell (Kaskade)', abrechnung: 'Monatlich · arithmetisch', }; Object.assign(window, { VERTRAGSMODELLE, MODELL_ORDER, CSTATUS, CSTATUS_ORDER, CSTATUS_RANK, ROLE_CONTRACTS, mieterSetFor, DOC_STATUS, UNIT_CONTRACTS, unitDocs, goLiveChecks, readinessPct, unitEvents, LEGAL_ENTITY, OBJEKT, });