← Launcher
`; } // ── SV-BERECHNUNG 2026 ──────────────────────────────────────────── function calcSV(brutto, settings, ma) { const s = settings; const g = v => parseFloat(s[v]||0); // Beitragsbemessungsgrenzen const bbgKV = g('bbg_kv_2026'); // 66.150 const bbgRV = g('bbg_rv_2026'); // 96.600 const baseKV = Math.min(brutto * 12, bbgKV) / 12; const baseRV = Math.min(brutto * 12, bbgRV) / 12; const kv_an = Math.round(baseKV * g('sv_kv_an') / 100 * 100) / 100; const pv_an = Math.round(baseKV * g('sv_pv_an') / 100 * 100) / 100; const rv_an = Math.round(baseRV * g('sv_rv_an') / 100 * 100) / 100; const av_an = Math.round(baseRV * g('sv_av_an') / 100 * 100) / 100; const kv_ag = Math.round(baseKV * g('sv_kv_ag') / 100 * 100) / 100; const pv_ag = Math.round(baseKV * g('sv_pv_ag') / 100 * 100) / 100; const rv_ag = Math.round(baseRV * g('sv_rv_ag') / 100 * 100) / 100; const av_ag = Math.round(baseRV * g('sv_av_ag') / 100 * 100) / 100; const sv_an_gesamt = kv_an + pv_an + rv_an + av_an; // Vereinfachte Lohnsteuer-Schätzung (Näherung – echte Berechnung via DATEV) const jBrutto = brutto * 12; const stKl = parseInt(ma.steuerklasse||'1'); const freibetrag = [0,11784,23568,11784,0,0,11784][stKl]||11784; const zvE = Math.max(0, jBrutto - freibetrag - sv_an_gesamt*12); let jLst = 0; if(zvE > 0 && zvE <= 17005) jLst = zvE * 0.14 * (1 - 5/zvE); else if(zvE <= 66760) jLst = (228.74 * zvE/10000 - 2397) * zvE/10000 + 965.58; else if(zvE <= 277825) jLst = (108.59 * zvE/10000 - 11602.21) * zvE/10000 + 16440; else jLst = zvE * 0.45 - 18936.88; const mLst = Math.max(0, Math.round(jLst / 12 * 100) / 100); // Soli (vereinfacht) const soliSatz = g('soli_satz') / 100; const soliFrei = g('soli_freigrenze'); const mSoli = jBrutto > soliFrei ? Math.round(mLst * soliSatz * 100) / 100 : 0; // Kirchensteuer const kstSatz = g('kst_bgl') / 100; const mKst = (ma.konfession && ma.konfession !== 'keine') ? Math.round(mLst * kstSatz * 100) / 100 : 0; const abzuege_an = sv_an_gesamt + mLst + mSoli + mKst; return { kv_an, pv_an, rv_an, av_an, sv_an_gesamt, kv_ag, pv_ag, rv_ag, av_ag, mLst, mSoli, mKst, abzuege_an, nettoApprox: Math.max(0, brutto - abzuege_an) }; } // ── PDF GUTSCHRIFT (HGB §84) ─────────────────────────────────────── function pdfHGB(e, bM, bJ, settings) { const ma = e.ma, r = e.regel; const n2 = v => new Intl.NumberFormat("de-DE",{minimumFractionDigits:2,maximumFractionDigits:2}).format(+v||0); const today = new Date().toLocaleDateString("de-DE",{day:"2-digit",month:"2-digit",year:"numeric"}); const belegNr = `${bJ}-${String(bM).padStart(2,"0")}-${ma.kuerzel}`; const adresse = [ma.strasse&&ma.hausnr?`${ma.strasse} ${ma.hausnr}`:"", ma.plz&&ma.ort?`${ma.plz} ${ma.ort}`:""].filter(Boolean).join("
"); const stNr = ma.steuernummer || "⚠️ nicht hinterlegt"; const ustid = ma.ustid || ""; const beedooUstId = settings.beedoo_ustid || "DE363829401"; const beedooStNr = settings.beedoo_steuernr || "320/5706/4015"; const abzugSum = e.abzug_summe||0; const gesamtAbzuege = abzugSum + (e.krank_abzug||0) + (e.vorschuss||0); const posRows = e.pos.length > 0 ? e.pos.map(p=>`${p.nr||"–"}${p.kunde||"–"}${p.info}${p.sa?" ⚡":""}${n2(p.betrag)}`).join("") : `Keine Einzelpositionen`; const rightTable = e.abzuege&&e.abzuege.length>0 ? `
Abzüge-Konto
${e.abzuege.map(a=>``).join("")}
BezeichnungTypBetrag
${(ABZ_KAT[a.kategorie]||ABZ_KAT.sonstiges).icon} ${a.bezeichnung}${a.typ==="wiederkehrend"?"↻ mtl.":"1×"}${n2(a.betrag)}
Summe${n2(abzugSum)}
` : r&&r.staffel_schwelle<999 ? `
Staffel-Modell
${e.staf_ok?``:""}
Basis je Sale${n2(r.basis_betrag_sale)}
Staffel ab${r.staffel_schwelle} Sales
Staffelrate${n2(r.staffel_betrag_sale)}
Erreicht?${e.staf_ok?"✓ Ja ("+e.sales+" Sales)":"Nein ("+e.sales+"/"+r.staffel_schwelle+")"}
Staffelbonus+${n2(e.staf_bonus)}
` : `
Sales ${MOL[bM]} ${bJ}
Abgerechnete Sales${e.sales}
Rate je Sale${r?n2(r.basis_betrag_sale):"–"}
`; const footer = `
Firmensitzbee-doo GmbH
Am Stadtholz 39
D-33609 Bielefeld
Kontaktwww.bee-doo.de
info@bee-doo.de
Fon: 0800 22 33 664
BankverbindungIBAN DE90 2545 0110
0031 0399 85
BIC NOLADE21SWB
GeschäftsführerPatrick Grabowski
Olaf Schader
${settings.beedoo_hrnr||"HRB 201542"} ${settings.beedoo_amtsgericht||"Hannover"}
`; return ` Gutschrift ${belegNr}
Telecommunication · eBusiness · Solar & Wärmepumpe · Sales-Promotion USt-IdNr.: ${beedooUstId} · Steuernummer: ${beedooStNr}
bee-doo
Energy for you
Datum: ${today}
Leistungszeitraum: ${bJ}/${String(bM).padStart(2,"0")}
bee-doo GmbH · Am Stadtholz 39 · D-33609 Bielefeld
${ma.vorname} ${ma.nachname}
${adresse ? `
${adresse}
` : `
⚠️ Adresse nicht hinterlegt
`}
${stNr ? `Steuernr.: ${stNr}` : ""}${ustid ? ` · USt-IdNr.: ${ustid}` : ""}
Gutschrift-Belegnummer: ${belegNr}
${ma.vorname} ${ma.nachname}  ·  Leistungszeitraum ${MOL[bM]} ${bJ}
${e.staf_ok?``:""} ${e.vorschuss>0?``:""} ${abzugSum>0?``:""}
Provisionen (Vermittlung Solar/WP)${n2(e.gesamt)}
Staffelbonus (${e.sales} Sales)+${n2(e.staf_bonus)}
Rückforderungen0,00
Zwischensumme${n2(e.gesamt - (e.vorschuss||0) - abzugSum)}
Vorschuss-Verrechnung-${n2(e.vorschuss)}
Sachkosten-Abzüge-${n2(abzugSum)}
Umsatzsteuer (gem. §19 Abs. 1 UStG)0,00
Auszahlungssumme${n2(e.netto)}
${rightTable}
Gem. Kleinunternehmerregelung §19 Abs. 1 UStG von der Umsatzsteuer befreit. Diese Gutschrift gemäß §14 Abs. 2 UStG ersetzt die Rechnung des Leistungserbringers. Empfänger: ${ma.vorname} ${ma.nachname}${ma.steuernummer?`, Steuernr. ${ma.steuernummer}`:""}. Aussteller: bee-doo GmbH, ${beedooUstId}.
${footer}
Provisionen ${MOL[bM]} ${bJ}
${ma.vorname} ${ma.nachname} (${ma.kuerzel})
Zusammenfassung
MitarbeiterAnzahlSumme
[${ma.kuerzel}] ${ma.vorname} ${ma.nachname}${e.pos.length}${n2(e.gesamt)}
Summe:${e.pos.length}${n2(e.netto)}
Einzelpositionen
${posRows} ${e.staf_ok&&r?``:""}
AuftragKundeArtProvision
⚡ Staffelbonus: ${e.sales} Sales ≥ ${r.staffel_schwelle}+${n2(e.staf_bonus)}
Summe${n2(e.gesamt)}
${footer}
`; } // ── PDF ENTGELTABRECHNUNG (Angestellte) ─────────────────────────── function pdfAngestellt(e, bM, bJ, settings) { const ma = e.ma, r = e.regel; const n2 = v => new Intl.NumberFormat("de-DE",{minimumFractionDigits:2,maximumFractionDigits:2}).format(+v||0); const today = new Date().toLocaleDateString("de-DE",{day:"2-digit",month:"2-digit",year:"numeric"}); const belegNr = `ENTG-${bJ}-${String(bM).padStart(2,"0")}-${ma.kuerzel}`; const brutto = e.gesamt + (parseFloat(ma.grundgehalt)||0); const sv = calcSV(brutto, settings, ma); const abzugSum = e.abzug_summe||0; const nettoAusz = Math.max(0, brutto - sv.abzuege_an - (e.vorschuss||0) - abzugSum - (e.krank_abzug||0)); const adresse = [ma.strasse&&ma.hausnr?`${ma.strasse} ${ma.hausnr}`:"", ma.plz&&ma.ort?`${ma.plz} ${ma.ort}`:""].filter(Boolean).join(", "); const gebDat = ma.geburtsdatum ? new Date(ma.geburtsdatum).toLocaleDateString("de-DE") : "–"; const beedooStNr = settings.beedoo_steuernr || "320/5706/4015"; const beedooHR = `${settings.beedoo_hrnr||"HRB 201542"} ${settings.beedoo_amtsgericht||"Hannover"}`; const footer = `
Arbeitgeberbee-doo GmbH
Am Stadtholz 39
D-33609 Bielefeld
RegistrierungSteuernr. ${beedooStNr}
${beedooHR}
www.bee-doo.de
BankverbindungIBAN DE90 2545 0110
0031 0399 85
BIC NOLADE21SWB
GeschäftsführerPatrick Grabowski
Olaf Schader
info@bee-doo.de
`; const row = (l,v,clr,bold,bg,indent) => ` ${l} ${v} `; return ` Entgeltabrechnung ${belegNr}
bee-doo
Energy for you
ENTGELTABRECHNUNG
${MOL[bM]} ${bJ}  ·  Belegnr.: ${belegNr}
Erstellt: ${today}
Arbeitnehmer
${ma.vorname} ${ma.nachname}
${adresse||"⚠️ Adresse fehlt"}
Steuer-ID: ${ma.steuer_id||"⚠️ fehlt"}  ·  SVNR: ${ma.svnr||"⚠️ fehlt"}
Steuerklasse: ${ma.steuerklasse||"–"}  ·  Kirchensteuer: ${ma.konfession&&ma.konfession!=="keine"?ma.konfession:"keine"}
Krankenkasse: ${ma.kv_kasse||"–"}
Geburtsdatum: ${gebDat}
Arbeitgeber
bee-doo GmbH
Am Stadtholz 39, D-33609 Bielefeld
Steuernr.: ${beedooStNr}
Beschäftigt seit: ${ma.eintrittsdatum?new Date(ma.eintrittsdatum).toLocaleDateString("de-DE"):"–"}
Beschäftigungsart: Angestellt / Vollzeit
Abrechnungszeitraum: 01.${String(bM).padStart(2,"0")}.${bJ} – ${new Date(bJ,bM,0).getDate()}.${String(bM).padStart(2,"0")}.${bJ}
Lohn- und Gehaltsbestandteile
${row("Grundgehalt",n2(ma.grundgehalt||0),"","","")} ${e.pos.length>0?row("Provisionen (variable Vergütung)",n2(e.gesamt),"","",""):""} ${e.staf_ok?row("⚡ Staffelbonus","+"+n2(e.staf_bonus),"#92400e","","#fffbeb"):""} ${row("BRUTTO-ENTGELT",n2(brutto),"#111",true,"#f9f9f9")} ${row("Krankenversicherung AN ("+n2(parseFloat(settings.sv_kv_an||7.3))+"%)","-"+n2(sv.kv_an),"#991b1b","","","yes")} ${row("Pflegeversicherung AN ("+n2(parseFloat(settings.sv_pv_an||1.7))+"%)","-"+n2(sv.pv_an),"#991b1b","","","yes")} ${row("Rentenversicherung AN ("+n2(parseFloat(settings.sv_rv_an||9.3))+"%)","-"+n2(sv.rv_an),"#991b1b","","","yes")} ${row("Arbeitslosenversicherung AN ("+n2(parseFloat(settings.sv_av_an||1.3))+"%)","-"+n2(sv.av_an),"#991b1b","","","yes")} ${row("Summe Sozialversicherung AN","-"+n2(sv.sv_an_gesamt),"#991b1b",true,"")} ${row("Lohnsteuer (Kl. "+( ma.steuerklasse||"1")+" – Näherungswert*)","-"+n2(sv.mLst),"#991b1b","","")} ${row("Solidaritätszuschlag","-"+n2(sv.mSoli),"#991b1b","","")} ${row("Kirchensteuer"+(ma.konfession&&ma.konfession!=="keine"?" ("+ma.konfession+")":" (keine)"),"-"+n2(sv.mKst),"#991b1b","","")} ${e.krank_abzug>0?row("Kranktage-Abzug ("+( e.krank?.tage||0)+" Tage)","-"+n2(e.krank_abzug),"#991b1b","","#fef2f2"):""} ${e.vorschuss>0?row("Vorschuss-Verrechnung","-"+n2(e.vorschuss),"#991b1b","","#fef2f2"):""} ${abzugSum>0?row("Sachkosten-Abzüge (Fahrzeug/Equipment)","-"+n2(abzugSum),"#991b1b","","#fef2f2"):""} ${row("NETTO-AUSZAHLUNGSBETRAG",n2(nettoAusz),"#166534",true,"#f0fdf4")}
PositionBetrag €
Gesetzliche Abzüge Arbeitnehmer
Arbeitgeberanteile (zur Information)
KV AG (${n2(settings.sv_kv_ag||7.3)}%)
${n2(sv.kv_ag)} €
PV AG (${n2(settings.sv_pv_ag||1.7)}%)
${n2(sv.pv_ag)} €
RV AG (${n2(settings.sv_rv_ag||9.3)}%)
${n2(sv.rv_ag)} €
AV AG (${n2(settings.sv_av_ag||1.3)}%)
${n2(sv.av_ag)} €
Gesamtkosten AG: ${n2(brutto + sv.kv_ag + sv.pv_ag + sv.rv_ag + sv.av_ag)} €
* Die Lohnsteuer ist ein Näherungswert. Die rechtsverbindliche Abrechnung erfolgt durch den Steuerberater / DATEV. Diese Abrechnung dient der internen Information gem. §108 GewO.
Bitte prüfen Sie die Angaben und wenden Sie sich bei Abweichungen an die Buchhaltung: info@bee-doo.de
${footer}
`; } // ── MAIN PDF DISPATCHER ─────────────────────────────────────────── async function doPDF(e, bM, bJ, settingsArg) { const ma = e.ma; const isHgb = ma.typ.startsWith("hgb"); // Settings aus Supabase holen falls nicht übergeben let settings = settingsArg || {}; if(!settingsArg) { try { const res = await fetch(SB+"/rest/v1/pay_settings", {headers: H}); const arr = await res.json(); settings = Object.fromEntries(arr.map(s=>[s.key, s.value])); } catch(err) { console.warn("Settings nicht geladen:", err); } } // MA-Daten nochmal frisch holen (mit neuen Feldern) try { const res = await fetch(SB+"/rest/v1/pay_mitarbeiter?id=eq."+ma.id+"&select=*", {headers: H}); const arr = await res.json(); if(arr[0]) Object.assign(e.ma, arr[0]); } catch(err) { console.warn("MA-Daten nicht nachgeladen:", err); } const html = isHgb ? pdfHGB(e, bM, bJ, settings) : pdfAngestellt(e, bM, bJ, settings); const w = window.open("", "_blank"); w.document.write(html); w.document.close(); setTimeout(()=>w.print(), 600); } // ── ATOMS ───────────────────────────────────────────────────────── const Spi=({s=18,color})=>
; const Badge=({c,bg,l})=>{l}; const Chip=({c,l})=>{l}; const Pill=({c,bg,l,n})=>
{l}
{n}
; const IBtn=({onClick,label,color,disabled})=>; const LabelRow=({k,v,vc})=>
{k}{v}
; function Delta({curr,prev}){ if(prev===undefined||prev===null)return; const d=curr-prev;if(Math.abs(d)<1)return±0; return0?C.G:C.R,fontSize:10,fontWeight:700,whiteSpace:"nowrap"}}>{d>0?"▲":"▼"} {eur(Math.abs(d))} } function NumEdit({val,onSave,min=0,max=99,color=C.Y}){ const [v,setV]=useState(String(val));const [ed,setEd]=useState(false); if(!ed)returnsetEd(true)} style={{color,fontWeight:700,fontSize:12,cursor:"pointer",borderBottom:`1px dashed ${color}50`,padding:"0 2px"}}>{val||"–"}; returnsetV(e.target.value)} onBlur={()=>{onSave(Math.max(min,Math.min(max,+v||0)));setEd(false)}} onKeyDown={e=>{if(e.key==="Enter"){onSave(Math.max(min,Math.min(max,+v||0)));setEd(false)}if(e.key==="Escape")setEd(false)}} style={{width:36,background:C.sur,border:`1px solid ${color}`,borderRadius:4,padding:"1px 4px",fontSize:11,textAlign:"center",color}} /> } function Toast({msg,type,onClose}){ useEffect(()=>{const t=setTimeout(onClose,4000);return()=>clearTimeout(t)},[]); return
{type==="error"?"⚠️":"✓"} {msg}
} // ── ABZ MODAL COMPONENT ─────────────────────────────────────────── function AbzModal({alle,mitarb,bM,bJ,onClose,onChange,showToast}){ const LEER={mitarbeiter_id:"",bezeichnung:"",betrag:"",typ:"wiederkehrend",kategorie:"auto",monat_start:bM,jahr_start:bJ,monat_ende:"",jahr_ende:"",notiz:""}; const [form,setForm]=useState(LEER); const [saving,setSaving]=useState(false); const [expandMa,setExpMa]=useState(null); // Alle aktiven Abzüge gruppiert nach MA const gruppiertAlle=useMemo(()=>{ const map={}; alle.filter(a=>a.aktiv).forEach(a=>{ if(!map[a.mitarbeiter_id])map[a.mitarbeiter_id]=[]; map[a.mitarbeiter_id].push(a); }); return map; },[alle]); const save=async()=>{ if(!form.mitarbeiter_id||!form.bezeichnung||!form.betrag)return; setSaving(true); try{ const payload={ mitarbeiter_id:form.mitarbeiter_id,bezeichnung:form.bezeichnung,betrag:+form.betrag, typ:form.typ,kategorie:form.kategorie, monat_start:+form.monat_start,jahr_start:+form.jahr_start,notiz:form.notiz,aktiv:true, ...(form.typ==="wiederkehrend"&&form.monat_ende&&form.jahr_ende?{monat_ende:+form.monat_ende,jahr_ende:+form.jahr_ende}:{monat_ende:null,jahr_ende:null}) }; const res=await db.post("pay_abzuege",payload); onChange([...alle,res[0]]); setForm(LEER); showToast("Abzug gespeichert ✓"); }catch(e){showToast(e.message,"error");} finally{setSaving(false);} }; const toggle=async(id,aktiv)=>{ try{await db.patch("pay_abzuege","id=eq."+id,{aktiv:!aktiv});onChange(alle.map(a=>a.id===id?{...a,aktiv:!aktiv}:a));}catch(e){showToast(e.message,"error");} }; const remove=async(id)=>{ if(!confirm("Abzug wirklich löschen?"))return; try{await db.del("pay_abzuege","id=eq."+id);onChange(alle.filter(a=>a.id!==id));showToast("Abzug gelöscht");}catch(e){showToast(e.message,"error");} }; const maHatAktiv=maId=>gruppe=>gruppiertAlle[maId]?.some(a=>abzugAktivInMonat(a,bM,bJ)); const totalAktiv=mitarb.reduce((s,m)=>{const active=getAbzuegeForMa(alle,m.id,bM,bJ);return s+active.reduce((x,a)=>x+(+a.betrag),0)},0); return(
e.stopPropagation()}> {/* Header */}
➖ Abzüge verwalten
{MO[bM]} {bJ} · Gesamt aktiv: {eur(totalAktiv)}
{/* Neuen Abzug anlegen */}
+ Neuen Abzug anlegen
{/* Zeile 1: MA + Typ */}
Mitarbeiter *
Typ *
{[["einmalig","1× Einmalig"],["wiederkehrend","↻ Monatlich"]].map(([k,l])=>( ))}
{/* Zeile 2: Bezeichnung */}
Bezeichnung *
setForm(p=>({...p,bezeichnung:e.target.value}))} placeholder='z.B. "VW Polo – Leasingrate"' className="inp"/>
{/* Zeile 3: Betrag + Kategorie */}
Betrag € *
setForm(p=>({...p,betrag:e.target.value}))} placeholder="z.B. 280" className="inp"/>
Kategorie
{/* Zeile 4: Zeitraum */}
{form.typ==="einmalig"?"Monat (einmalig)":"Startmonat"}
{const[y,m]=e.target.value.split("-");setForm(p=>({...p,monat_start:+m,jahr_start:+y}))}} className="inp"/>
{form.typ==="wiederkehrend"&&(
Endet (leer = unbegrenzt)
{if(!e.target.value){setForm(p=>({...p,monat_ende:"",jahr_ende:""}));return;}const[y,m]=e.target.value.split("-");setForm(p=>({...p,monat_ende:+m,jahr_ende:+y}))}} className="inp"/>
)}
{/* Notiz */}
Notiz / Grund
setForm(p=>({...p,notiz:e.target.value}))} placeholder="Optionale Erläuterung" className="inp"/>
{/* Bestehende Abzüge nach MA */}
Alle Abzüge ({alle.filter(a=>a.aktiv).length} aktiv)
{mitarb.map(ma=>{ const maAbzuege=alle.filter(a=>a.mitarbeiter_id===ma.id); if(maAbzuege.length===0)return null; const aktivImMonat=getAbzuegeForMa(alle,ma.id,bM,bJ); const summeMonat=aktivImMonat.reduce((s,a)=>s+(+a.betrag),0); const isExp=expandMa===ma.id; return(
0?C.R+"30":C.b}`}}> {/* MA Header */} {isExp&&(
{maAbzuege.map(a=>{ const kat=ABZ_KAT[a.kategorie]||ABZ_KAT.sonstiges; const aktivJetzt=abzugAktivInMonat(a,bM,bJ); const laufend=a.typ==="wiederkehrend"; return(
{kat.icon}
{a.bezeichnung} {laufend?"↻ monatl.":"1× einmalig"} {aktivJetzt&&aktiv {MO[bM]}}
Ab {MO[a.monat_start]} {a.jahr_start} {laufend&&(a.monat_ende?` bis ${MO[a.monat_ende]} ${a.jahr_ende}`:" · unbegrenzt")} {a.notiz&& · {a.notiz}}
-{eur(a.betrag)}
{laufend&&
pro Monat
}
) })}
)}
) })} {alle.length===0&&
Noch keine Abzüge angelegt
}
) } // ── MAIN APP ────────────────────────────────────────────────────── // ── PIN LOCK ────────────────────────────────────────────────────── const SESSION_KEY = "beedoo_pay_auth"; function PinLock({onUnlock}){ const [pin,setPin]=useState(""); const [err,setErr]=useState(""); const [attempts,setAttempts]=useState(0); const [loading,setLd]=useState(false); const [locked,setLocked]=useState(false); const [lockLeft,setLockLeft]=useState(0); const [hint,setHint]=useState(""); const maxAttempts=5; const inputRef=useRef(); useEffect(()=>{ // Load hint from Supabase fetch(SB+"/rest/v1/pay_settings?key=in.(pay_pin_hint,pay_max_attempts)",{headers:H}) .then(r=>r.json()).then(arr=>{ const m=Object.fromEntries(arr.map(s=>[s.key,s.value])); if(m.pay_pin_hint) setHint(m.pay_pin_hint); }).catch(()=>{}); // Focus input setTimeout(()=>inputRef.current?.focus(),100); // Check lockout const lockUntil=+sessionStorage.getItem("pay_lock_until")||0; if(lockUntil>Date.now()){ setLocked(true); startCountdown(lockUntil); } const att=+sessionStorage.getItem("pay_attempts")||0; setAttempts(att); },[]); const startCountdown=(until)=>{ const tick=()=>{ const left=Math.ceil((until-Date.now())/1000); if(left<=0){setLocked(false);setLockLeft(0);sessionStorage.removeItem("pay_lock_until");sessionStorage.removeItem("pay_attempts");setAttempts(0);} else{setLockLeft(left);setTimeout(tick,1000);} };tick(); }; const handleKey=(e)=>{ if(e.key==="Enter") check(); // Numeric only if(!/[0-9]/.test(e.key)&&e.key!=="Backspace"&&e.key!=="Delete") e.preventDefault(); }; const check=async()=>{ if(locked||loading||pin.length<4) return; setLd(true);setErr(""); try{ const r=await fetch(SB+"/rest/v1/pay_settings?key=eq.pay_admin_pin",{headers:H}); const arr=await r.json(); const correctPin=arr[0]?.value||""; if(pin===correctPin){ // Success sessionStorage.setItem(SESSION_KEY,"1"); sessionStorage.removeItem("pay_attempts"); sessionStorage.removeItem("pay_lock_until"); setErr(""); onUnlock(); } else { const newAtt=(attempts+1); setAttempts(newAtt); sessionStorage.setItem("pay_attempts",newAtt); if(newAtt>=maxAttempts){ const until=Date.now()+5*60*1000; // 5 min lockout sessionStorage.setItem("pay_lock_until",until); setLocked(true); startCountdown(until); setErr("Zu viele Versuche – 5 Minuten gesperrt."); } else { setErr(`Falscher PIN. Noch ${maxAttempts-newAtt} Versuch${maxAttempts-newAtt!==1?"e":""}.`); setPin(""); setTimeout(()=>inputRef.current?.focus(),50); } } }catch(e){setErr("Verbindungsfehler – bitte erneut versuchen.");} finally{setLd(false);} }; const dots=Array.from({length:6}).map((_,i)=>(
)); return(
{/* Logo */}
bee-doo
PAY · Geschäftsführung
{/* Lock card */}
{locked?"🔒":"🔐"}
Zugang gesperrt
{locked?`Zu viele Fehlversuche – noch ${lockLeft}s`:"PIN eingeben um fortzufahren"}
{/* PIN dots */}
{dots}
{/* Hidden input */} { if(!locked) setPin(e.target.value.replace(/\D/g,"")); }} onKeyDown={handleKey} disabled={locked||loading} style={{ width:"100%",background:"#16161A",border:`1.5px solid ${err?"#F87171":"#2A2A33"}`, borderRadius:10,padding:"12px 16px",fontSize:20,textAlign:"center", color:"#F0F0F4",outline:"none",letterSpacing:6,fontFamily:"monospace", marginBottom:err?8:16 }} placeholder="• • • • •" /> {err&&
⚠ {err}
} {hint&&err&&!locked&&
Hinweis: {hint}
}
{/* Footer hint */}
bee-doo GmbH · Nur für Geschäftsführung
); } // ── WRAPPER (checks session) ────────────────────────────────────── function Root(){ const [auth,setAuth]=useState(()=>sessionStorage.getItem(SESSION_KEY)==="1"); if(!auth) return setAuth(true)}/>; return ; } function App(){ const [tab,setTab]=useState("main"); const [abrMonat,setAM]=useState("2026-02"); const [selMa,setSel]=useState(null); const [filter,setFilter]=useState("alle"); const [mitarb,setMA]=useState([]); const [regeln,setRG]=useState([]); const [auftraege,setAU]=useState([]); const [auftraegeVorm,setAUV]=useState([]); const [abr,setABR]=useState([]); const [abrVorm,setABRV]=useState([]); const [vorschuesse,setVS]=useState([]); const [kranktage,setKT]=useState([]); const [abzuege,setABZ]=useState([]); const [slackWH,setSlackWH]=useState(""); const [slackOn,setSlackOn]=useState(false); const [loading,setLd]=useState(true); const [saving,setSv]=useState({}); const [toast,setToast]=useState(null); const [showVSModal,setShowVS]=useState(false); const [showAbzModal,setShowAbz]=useState(false); const [showSettings,setShowSt]=useState(false); const [vsForm,setVSF]=useState({ma_id:"",betrag:"",grund:"",quelle:"manuell"}); const [vsLoading,setVSLd]=useState(false); const [ktSaving,setKTSv]=useState({}); const ok=msg=>setToast({msg,type:"success"}); const err=msg=>setToast({msg,type:"error"}); const [aJ,aMN]=abrMonat.split("-").map(Number); const bM=aMN===1?12:aMN-1, bJ=aMN===1?aJ-1:aJ; const vM=bM===1?12:bM-1, vJ=bM===1?bJ-1:bJ; useEffect(()=>{ setLd(true); const m1=String(bM).padStart(2,"0"),m2=bM===12?"01":String(bM+1).padStart(2,"0"),y2=bM===12?bJ+1:bJ; const vm1=String(vM).padStart(2,"0"),vm2=vM===12?"01":String(vM+1).padStart(2,"0"),vy2=vM===12?vJ+1:vJ; Promise.all([ db.get("pay_mitarbeiter","?aktiv=eq.true&order=nachname.asc"), db.get("provisions_regeln","?aktiv=eq.true"), db.get("pay_auftraege",`?auftrags_datum=gte.${bJ}-${m1}-01&auftrags_datum=lt.${y2}-${m2}-01&order=auftrags_datum.asc`), db.get("pay_auftraege",`?auftrags_datum=gte.${vJ}-${vm1}-01&auftrags_datum=lt.${vy2}-${vm2}-01`), db.get("pay_abrechnungen",`?monat=eq.${bM}&jahr=eq.${bJ}`), db.get("pay_abrechnungen",`?monat=eq.${vM}&jahr=eq.${vJ}`), db.get("pay_vorschuesse",`?monat=eq.${bM}&jahr=eq.${bJ}`), db.get("pay_krankheitstage",`?monat=eq.${bM}&jahr=eq.${bJ}`), db.get("pay_abzuege","?aktiv=eq.true&order=erstellt_am.desc"), db.get("pay_settings",""), ]).then(([ma,r,a,av,ab,abv,vs,kt,abz,settings])=>{ setMA(ma);setRG(r);setAU(a);setAUV(av);setABR(ab);setABRV(abv);setVS(vs);setKT(kt);setABZ(abz); const sMap=Object.fromEntries(settings.map(s=>[s.key,s.value])); setSlackWH(sMap.slack_webhook||"");setSlackOn(sMap.slack_enabled==="true"); }).catch(e=>err("Ladefehler: "+e.message)).finally(()=>setLd(false)); },[bM,bJ]); const {E,rel}=useMemo(()=>calcAll(auftraege,mitarb,regeln,vorschuesse,kranktage,abzuege,bM,bJ),[auftraege,mitarb,regeln,vorschuesse,kranktage,abzuege,bM,bJ]); const {E:EV}=useMemo(()=>calcAll(auftraegeVorm,mitarb,regeln,[],[],[],vM,vJ),[auftraegeVorm,mitarb,regeln,vM,vJ]); const eintraege=useMemo(()=>{ const AM=Object.fromEntries(abr.map(a=>[a.mitarbeiter_id,a])); let arr=Object.values(E).map(e=>({...e,abr:AM[e.ma.id]||null,status:AM[e.ma.id]?.status||"entwurf",abr_id:AM[e.ma.id]?.id||null,prevNetto:EV[e.ma.id]?.netto??null})).sort((a,b)=>b.gesamt-a.gesamt); if(filter==="offen") arr=arr.filter(e=>e.status==="entwurf"); if(filter==="freigeg") arr=arr.filter(e=>e.status==="freigegeben"); if(filter==="bezahlt") arr=arr.filter(e=>e.status==="ausgezahlt"); if(filter==="staffel") arr=arr.filter(e=>e.staf_ok); if(filter==="vorschuss") arr=arr.filter(e=>e.vorschuss>0); if(filter==="krank") arr=arr.filter(e=>e.krank?.tage>0); if(filter==="abzuege") arr=arr.filter(e=>e.abzug_summe>0); return arr; },[E,EV,abr,filter]); const setSt=useCallback(async(maId,ns)=>{ setSv(s=>({...s,[maId]:true})); try{ const e=eintraege.find(e=>e.ma.id===maId);if(!e)return; const pl={mitarbeiter_id:maId,monat:bM,jahr:bJ,status:ns,gesamt_betrag:e.gesamt,provisions_summe:e.gesamt,staffel_bonus:e.staf_bonus||0,staffel_erreicht:e.staf_ok||false,sales_count:e.sales, ...(ns==="freigegeben"?{freigegeben_am:new Date().toISOString()}:{}), ...(ns==="ausgezahlt"?{ausgezahlt_am:new Date().toISOString()}:{})}; const res=await db.upsert("pay_abrechnungen",pl); if(ns==="freigegeben"&&!e.abr_id){const aid=res[0]?.id;if(aid)await db.post("pay_provisionen",e.pos.map(p=>({auftrag_id:p.aid,mitarbeiter_id:maId,abrechnung_id:aid,typ:p.typ,betrag:p.betrag,monat_bezug:bM,jahr_bezug:bJ,status:"freigegeben",staffel_angewendet:p.sa||false,staffel_sales_count:e.sales,freigegeben_am:new Date().toISOString()})));} if(ns==="ausgezahlt"&&e.abr_id){ await db.patch("pay_provisionen","abrechnung_id=eq."+e.abr_id,{status:"bezahlt",bezahlt_am:new Date().toISOString()}); if(e.vorschuss>0)await db.patch("pay_vorschuesse","mitarbeiter_id=eq."+maId+"&monat=eq."+bM+"&jahr=eq."+bJ,{verrechnet_am:new Date().toISOString()}); } if(slackOn&&slackWH)await sendSlack(slackWH,slackBlocks(e,bM,bJ,ns)); setABR(prev=>{const idx=prev.findIndex(a=>a.mitarbeiter_id===maId&&a.monat===bM&&a.jahr===bJ);const upd=res[0]||{...pl,id:e.abr_id};if(idx>=0){const n=[...prev];n[idx]=upd;return n;}return[...prev,upd]}); ok(e.ma.vorname+" "+e.ma.nachname+" → "+(ns==="ausgezahlt"?"Ausgezahlt ✓":"Freigegeben ✓")+(slackOn?" · Slack ✓":"")); }catch(e){err(e.message);}finally{setSv(s=>({...s,[maId]:false}));} },[eintraege,bM,bJ,slackOn,slackWH]); const setAll=useCallback(async ns=>{for(const e of eintraege.filter(e=>ns==="freigegeben"?e.status==="entwurf":e.status==="freigegeben"))await setSt(e.ma.id,ns)},[eintraege,setSt]); const saveKranktage=useCallback(async(maId,tage)=>{ setKTSv(s=>({...s,[maId]:true})); try{const res=await db.upsert("pay_krankheitstage",{mitarbeiter_id:maId,monat:bM,jahr:bJ,tage,arbeitstage_monat:21});setKT(prev=>{const idx=prev.findIndex(k=>k.mitarbeiter_id===maId);const upd=res[0]||{mitarbeiter_id:maId,tage};if(idx>=0){const n=[...prev];n[idx]=upd;return n;}return[...prev,upd]});if(tage>0)ok("Kranktage: "+eur(krankenAbzug(+mitarb.find(m=>m.id===maId)?.grundgehalt||0,tage,21))+" Abzug");} catch(e){err(e.message);}finally{setKTSv(s=>({...s,[maId]:false}));} },[bM,bJ,mitarb]); const saveVS=async()=>{ if(!vsForm.ma_id||!vsForm.betrag)return;setVSLd(true); try{const res=await db.upsert("pay_vorschuesse",{mitarbeiter_id:vsForm.ma_id,betrag:+vsForm.betrag,monat:bM,jahr:bJ,grund:vsForm.grund,quelle:vsForm.quelle});setVS(prev=>{const idx=prev.findIndex(v=>v.mitarbeiter_id===vsForm.ma_id);const upd=res[0]||{mitarbeiter_id:vsForm.ma_id,betrag:+vsForm.betrag};if(idx>=0){const n=[...prev];n[idx]=upd;return n;}return[...prev,upd]});setVSF({ma_id:"",betrag:"",grund:"",quelle:"manuell"});setShowVS(false);ok("Vorschuss gespeichert ✓");} catch(e){err(e.message);}finally{setVSLd(false);} }; const delVS=async maId=>{try{await db.del("pay_vorschuesse","mitarbeiter_id=eq."+maId+"&monat=eq."+bM+"&jahr=eq."+bJ);setVS(prev=>prev.filter(v=>v.mitarbeiter_id!==maId));ok("Vorschuss gelöscht");}catch(e){err(e.message);}}; const saveSlack=async()=>{try{await db.upsert("pay_settings",[{key:"slack_webhook",value:slackWH},{key:"slack_enabled",value:String(slackOn)}]);ok("Einstellungen gespeichert ✓");setShowSt(false);}catch(e){err(e.message);}}; const testSlack=async()=>{if(!slackWH){err("Kein Webhook eingetragen");return;}await sendSlack(slackWH,[{type:"section",text:{type:"mrkdwn",text:"🟡 *bee-doo PAY* – Testbenachrichtigung ✓"}}]);ok("Testnachricht gesendet!");}; // Stats const allE=Object.values(E).map(e=>({...e,status:abr.find(a=>a.mitarbeiter_id===e.ma.id)?.status||"entwurf"})); const cA=allE.filter(e=>e.status==="ausgezahlt").length; const cF=allE.filter(e=>e.status==="freigegeben").length; const cO=allE.filter(e=>e.status==="entwurf").length; const totB=allE.reduce((s,e)=>s+e.gesamt,0); const totN=allE.reduce((s,e)=>s+e.netto,0); const totVS=vorschuesse.reduce((s,v)=>s+(+v.betrag),0); const totKR=allE.reduce((s,e)=>s+(e.krank_abzug||0),0); const totABZ=allE.reduce((s,e)=>s+(e.abzug_summe||0),0); const cStaf=allE.filter(e=>e.staf_ok).length; const cKrank=kranktage.filter(k=>k.tage>0).length; const cAbzMA=new Set(abzuege.filter(a=>abzugAktivInMonat(a,bM,bJ)).map(a=>a.mitarbeiter_id)).size; const cOff=auftraege.filter(a=>a.zahlungsstatus==="offen").length; const isSav=Object.values(saving).some(Boolean); const radar=Object.values(E).filter(e=>e.regel&&e.regel.staffel_schwelle<999&&!e.staf_ok&&e.regel.staffel_schwelle-e.sales<=3&&e.regel.staffel_schwelle-e.sales>0).sort((a,b)=>(a.regel.staffel_schwelle-a.sales)-(b.regel.staffel_schwelle-b.sales)); const selEntry=eintraege.find(e=>e.ma.id===selMa)||allE.find(e=>e.ma.id===selMa); // ── RESPONSIVE ──────────────────────────────────────────────── const [isMob,setIsMob]=useState(window.innerWidth<700); useEffect(()=>{const fn=()=>setIsMob(window.innerWidth<700);window.addEventListener("resize",fn);return()=>window.removeEventListener("resize",fn)},[]); const tabBtn=(id,lb,dis)=>; // ── MOBILE KARTEN-VIEW ──────────────────────────────────────── const MobileCard=({e})=>{ const sc=STCFG[e.status]||STCFG.entwurf; const tc=TCLR[e.ma.typ]||C.tx; const sw=e.regel?.staffel_schwelle; const iS=saving[e.ma.id]; const hasAbz=e.abzuege?.length>0; const totalAbz=e.abzug_summe||0; return(
{/* Kopfzeile */}
{e.ma.vorname} {e.ma.nachname}
{e.ma.kuerzel}{e.ma.typ.startsWith("angestellt")&&e.ma.grundgehalt>0?" · Basis "+eur(e.ma.grundgehalt):""}
{/* Staffel-Fortschritt */} {sw&&sw<999&&!e.staf_ok&&(
Staffel: {e.sales}/{sw} Sales {sw-e.sales}× fehlen
)} {e.staf_ok&&(
Staffel! {e.sales} Sales · +{eur(e.staf_bonus)}
)} {/* Zahlen-Block */}
{/* Zeile 1: Provision + Abzüge */}
Provision
{eur(e.gesamt)}
{(e.vorschuss>0||hasAbz||e.krank_abzug>0)&&(
Abzüge
{[e.vorschuss>0&&`-${eur(e.vorschuss)}`,totalAbz>0&&`-${eur(totalAbz)}`,e.krank_abzug>0&&`🤒-${eur(e.krank_abzug)}`].filter(Boolean).join(" ")}
)}
{/* Netto-Balken */}
NETTO {eur(e.netto)} {e.prevNetto!==null&&}
{/* Aktionen */}
{iS?:<> {e.status==="entwurf"&&( )} {e.status==="freigegeben"&&( )} {e.status==="ausgezahlt"&&(
✓ Ausgezahlt
)} }
) }; return(
{toast&&setToast(null)}/>} {showAbzModal&&setShowAbz(false)} onChange={setABZ} showToast={(m,t)=>setToast({msg:m,type:t||"success"})}/>} {/* ═══ HEADER ═══════════════════════════════════════════════ */} {isMob?( /* ── MOBILE HEADER ── */
bee-dooPAY
{isSav&&} {cOff>0&&⚠️ {cOff}} setAM(e.target.value)} style={{background:C.card,border:`1px solid ${C.bl}`,color:C.tx,borderRadius:6,padding:"5px 8px",fontSize:12}}/>
{/* Tabs als Bottom-Nav-Style */}
{tabBtn("main","Übersicht")} {tabBtn("detail","Detail",!selMa)} {tabBtn("modelle","Modelle")}
):( /* ── DESKTOP HEADER ── */
bee-dooPAY
{tabBtn("main","Übersicht")}{tabBtn("detail","Detail",!selMa)}{tabBtn("modelle","Modelle")}
{isSav&&} {cOff>0&&⚠️ {cOff} offen} {slackOn&&🟢} setAM(e.target.value)} style={{background:C.card,border:`1px solid ${C.bl}`,color:C.tx,borderRadius:5,padding:"3px 8px",fontSize:11,width:128}}/> exportDATEV(eintraege,bM,bJ)} label="⬇ DATEV" color={C.G}/> (async()=>{const res=await fetch(SB+"/rest/v1/pay_settings",{headers:H});const arr=await res.json();const s=Object.fromEntries(arr.map(x=>[x.key,x.value]));for(const e of eintraege){await doPDF(e,bM,bJ,s);await new Promise(r=>setTimeout(r,800))}})()} label="🖨️ PDFs" color={C.Y}/>
)} {/* ═══ KPI STRIP ════════════════════════════════════════════ */} {isMob?( /* ── MOBILE KPI: 2×2 Grid ── */
Brutto Provision
{eur(totB)}
Netto Auszahlung
{eur(totN)}
{[ {c:C.G, bg:C.GD, l:`✓ Ausgezahlt (${cA})`, n:eur(allE.filter(e=>e.status==="ausgezahlt").reduce((s,e)=>s+e.netto,0))}, {c:C.B, bg:C.BD, l:`Freigegeben (${cF})`, n:eur(allE.filter(e=>e.status==="freigegeben").reduce((s,e)=>s+e.netto,0))}, {c:C.O, bg:C.OD, l:`Offen (${cO})`, n:eur(allE.filter(e=>e.status==="entwurf").reduce((s,e)=>s+e.gesamt,0))}, ...(totVS>0?[{c:C.R,bg:C.RD,l:`Vorschüsse (${vorschuesse.length})`,n:eur(totVS)}]:[]), ...(totABZ>0?[{c:"#F472B6",bg:"#F472B615",l:`Abzüge (${cAbzMA} MA)`,n:eur(totABZ)}]:[]), ...(totKR>0?[{c:C.T,bg:C.TD,l:`Krank (${cKrank}×)`,n:eur(totKR)}]:[]), ...(cStaf>0?[{c:C.P,bg:C.PD,l:`Staffeln`,n:cStaf+" ⚡"}]:[]), ].map((p,i)=>(
{p.l}
{p.n}
))}
{radar.length>0&&
⚡ Staffel-Radar {radar.map(e=>{e.ma.kuerzel} –{e.regel.staffel_schwelle-e.sales}×)}
}
):( /* ── DESKTOP KPI ── */
e.status==="ausgezahlt").reduce((s,e)=>s+e.netto,0))} bg={C.GD}/> e.status==="freigegeben").reduce((s,e)=>s+e.netto,0))} bg={C.BD}/> e.status==="entwurf").reduce((s,e)=>s+e.gesamt,0))} bg={C.OD}/>
{radar.length>0&&
⚡ Radar {radar.map(e=>{e.ma.kuerzel} –{e.regel.staffel_schwelle-e.sales}x)}
}
)} {/* ═══ ACTION BAR ═══════════════════════════════════════════ */} {isMob?( /* ── MOBILE ACTION BAR ── */
{/* Filter scroll */}
{[["alle","Alle",C.ts],["offen","Offen",C.O],["freigeg","Freigeg.",C.B],["bezahlt","Ausgez.",C.G],["staffel","⚡ Staffel",C.Y],["vorschuss","Vorschuss",C.R],["abzuege","Abzüge","#F472B6"],["krank","🤒 Krank",C.T]].map(([k,l,c])=>( ))}
):( /* ── DESKTOP ACTION BAR ── */
setAll("freigegeben")} label={`✓ Alle freigeben (${cO})`} color={C.B} disabled={cO===0||isSav}/> setAll("ausgezahlt")} label={`💸 Alle auszahlen (${cF})`} color={C.G} disabled={cF===0||isSav}/>
setShowVS(true)} label="+ Vorschuss" color={C.R}/> setShowAbz(true)} label="➖ Abzüge" color={"#F472B6"}/> { setSv(s=>({...s,_portal:true})); let n=0; for(const e of eintraege){try{await saveToPortal(e,bM,bJ);n++;}catch(err){}} setSv(s=>({...s,_portal:false})); ok(`📤 ${n} Abrechnungen ins Portal gespeichert ✓`); }} label={`📤 Alle ins Portal (${eintraege.length})`} color={C.T} disabled={eintraege.length===0||saving._portal}/>
{[["alle","Alle",C.ts],["offen","Offen",C.O],["freigeg","Freigeg.",C.B],["bezahlt","Ausgez.",C.G],["staffel","⚡",C.Y],["vorschuss","Vorschuss",C.R],["abzuege","Abzüge","#F472B6"],["krank","🤒 Krank",C.T]].map(([k,l,c])=>( ))}
{eintraege.length} Einträge · {MO[bM]} {bJ}
)} {/* ═══ CONTENT ══════════════════════════════════════════════ */}
{loading?(
Lade Daten...
):tab==="main"&&isMob?( /* ─── MOBILE KARTEN-LIST ────────────────────────────────── */
{eintraege.length===0?(
Keine Einträge für diesen Filter
):eintraege.map(e=>)} {/* Summen-Footer */} {eintraege.length>0&&
Gesamt · {eintraege.length} Mitarbeiter
Brutto
{eur(totB)}
Abzüge
-{eur(totVS+totABZ+totKR)}
Netto
{eur(totN)}
}
):tab==="main"?( /* ─── HAUPTTABELLE ──────────────────────────────────────── */
{[["Mitarbeiter","20%"],["Typ","8%"],["Sales","5%"],["Staffel","9%"],["Krank","7%"],["Provision","8%"],["Vorschuss","7%"],["Abzüge","9%"],["Netto","8%"],["vs "+MO[vM],"7%"],["Status","5%"],["","7%"]].map(([h,w])=>( ))} {eintraege.length===0?( ):eintraege.map((e,i)=>{ const sc=STCFG[e.status]||STCFG.entwurf; const tc=TCLR[e.ma.typ]||C.tx; const sw=e.regel?.staffel_schwelle; const isA=e.ma.typ.startsWith("angestellt"); const iS=saving[e.ma.id]; const iKS=ktSaving[e.ma.id]; // Abzüge dieses MA kompakt const abzTop=e.abzuege.slice(0,2); const abzRest=e.abzuege.length-2; return( {setSel(e.ma.id);setTab("detail")}}> ) })} {eintraege.length>0&&}
{h}
Keine Einträge für diesen Filter
{e.ma.vorname} {e.ma.nachname}
{e.ma.kuerzel}{isA&&e.ma.grundgehalt>0?" · "+eur(e.ma.grundgehalt):""}
{e.sales}
{sw&&sw<999&&
/{sw}
}
{e.staf_ok?⚡ +{eur(e.staf_bonus)} :sw&&sw<999?
{sw-e.sales}x fehlt
:}
ev.stopPropagation()}> {isA?
{iKS?:null}saveKranktage(e.ma.id,v)} color={C.T}/>d
{(e.krank_abzug||0)>0&&-{eur(e.krank_abzug)}}
:}
{eur(e.gesamt)} {e.vorschuss>0?-{eur(e.vorschuss)}:} ev.stopPropagation()}> {e.abzuege.length>0?(
{abzTop.map((a,i)=>{const kat=ABZ_KAT[a.kategorie]||ABZ_KAT.sonstiges;return(
{kat.icon} {a.bezeichnung} -{eur(a.betrag)} {a.typ==="wiederkehrend"&&}
)})} {abzRest>0&&
+{abzRest} weitere
}
Σ -{eur(e.abzug_summe)}
):}
{eur(e.netto)} ev.stopPropagation()}> ev.stopPropagation()}>
{iS?:<> {e.status==="entwurf" &&} {e.status==="freigegeben" &&} }
GESAMT · {eintraege.length} Mitarbeiter -{eur(totKR)} {eur(totB)} -{eur(totVS)} -{eur(totABZ)} {eur(totN)}
):tab==="detail"&&selEntry?( /* ─── DETAIL ────────────────────────────────────────────── */

{selEntry.ma.vorname} {selEntry.ma.nachname}

{selEntry.status&&} {selEntry.prevNetto!==null&&vs {MO[vM]}: }
{MOL[bM]} {bJ} · {selEntry.pos?.length||0} Positionen
{saving[selEntry.ma.id]?:<> {selEntry.status==="entwurf" &&setSt(selEntry.ma.id,"freigegeben")} label="✓ Freigeben" color={C.B}/>} {selEntry.status==="freigegeben" &&setSt(selEntry.ma.id,"ausgezahlt")} label="💸 Ausgezahlt" color={C.G}/>} {selEntry.status==="ausgezahlt" &&✓ Ausgezahlt} } setShowAbz(true)} label="➖ Abzüge" color={"#F472B6"}/> doPDF(selEntry,bM,bJ)} label="🖨️ PDF" color={C.Y}/> {setSv(s=>({...s,portalDet:true}));try{await saveToPortal(selEntry,bM,bJ);ok(selEntry.ma.kuerzel+" → Portal ✓");}catch(ex){err(ex.message);}finally{setSv(s=>({...s,portalDet:false}));}}} label={saving.portalDet?"⏳":"📤 Portal"} color={C.T}/>
{selEntry.staf_ok&&
Staffel! {selEntry.sales} Sales ≥ {selEntry.regel?.staffel_schwelle}
Rückwirkend {eur(selEntry.regel?.staffel_betrag_sale)}/Sale · +{eur(selEntry.staf_bonus)}
} {selEntry.krank_abzug>0&&
🤒
{selEntry.krank?.tage} Kranktage · -{eur(selEntry.krank_abzug)}
{selEntry.krank?.notiz||"Grundgehalt anteilig"}
} {selEntry.vorschuss>0&&
Vorschuss -{eur(selEntry.vorschuss)}
} {selEntry.abzuege?.length>0&&
➖ Abzüge ({selEntry.abzuege.length}) · -{eur(selEntry.abzug_summe)}
{selEntry.abzuege.map(a=>{const kat=ABZ_KAT[a.kategorie]||ABZ_KAT.sonstiges;return(
{kat.icon} {a.bezeichnung} {a.typ==="wiederkehrend"?"↻ mtl.":"1×"} {a.notiz&&· {a.notiz}} -{eur(a.betrag)}
)})}
}
Abrechnung
{(selEntry.pos||[]).map((p,i)=>( ))} {(selEntry.abzuege||[]).map(a=>{const kat=ABZ_KAT[a.kategorie]||ABZ_KAT.sonstiges;return})} {selEntry.krank_abzug>0&&} {selEntry.vorschuss>0&&}
{p.nr}
{p.kunde}
{PICS[p.typ]||"•"} {p.info}{p.sa&&} {eur(p.betrag)}
Brutto{eur(selEntry.gesamt)}
{kat.icon} {a.bezeichnung}{a.typ==="wiederkehrend"?" (↻)":""}-{eur(a.betrag)}
🤒 Krank ({selEntry.krank?.tage}d)-{eur(selEntry.krank_abzug)}
✋ Vorschuss-{eur(selEntry.vorschuss)}
NETTO{eur(selEntry.netto)}
Mitarbeiter
{[["Typ",TLBL[selEntry.ma.typ]||selEntry.ma.typ],...(selEntry.ma.typ.startsWith("angestellt")?[["Grundgehalt",eur(selEntry.ma.grundgehalt)]]:[] ),...(selEntry.ma.auto_zuschuss>0?[["Auto",eur(selEntry.ma.auto_zuschuss)+"/Mo"]]:[] ),["Sales",selEntry.sales],...(selEntry.prevNetto!==null?[["Vormonat",eur(selEntry.prevNetto)]]:[] )].map(([k,v])=>)} {selEntry.ma.typ.startsWith("angestellt")&&
🤒 Kranktage
{ktSaving[selEntry.ma.id]?:null}saveKranktage(selEntry.ma.id,v)} color={C.T}/>Tage · -{eur(selEntry.krank_abzug)}
}
{selEntry.regel&&
Modell
{[["Basis",eur(selEntry.regel.basis_betrag_sale)],selEntry.regel.staffel_schwelle<999&&["Staffel",`${selEntry.regel.staffel_schwelle} → ${eur(selEntry.regel.staffel_betrag_sale)}`],selEntry.regel.rueckwirkend&&["Rückwirkend","✓"],+selEntry.regel.betrag_termin_sf>0&&["Termin SF",eur(selEntry.regel.betrag_termin_sf)],+selEntry.regel.betrag_eigenlead>0&&["Eigenlead",eur(selEntry.regel.betrag_eigenlead)]].filter(Boolean).map(([k,v])=>)}
}
{eintraege.map(e=>)}
):tab==="modelle"?( /* ─── MODELLE ───────────────────────────────────────────── */

Provisions-Modelle

{regeln.length} aktiv
{regeln.map(r=>{const tc=TCLR[r.mitarbeiter_typ]||C.tx;return(
{r.modell_name}
{r.mitarbeiter_typ}
{[["Sale Basis",eur(r.basis_betrag_sale)],r.staffel_schwelle<999&&["Staffel ab",r.staffel_schwelle+" Sales"],r.staffel_schwelle<999&&["Staffel Rate",eur(r.staffel_betrag_sale)],r.rueckwirkend&&["Rückwirkend","✓"],+r.betrag_termin_sf>0&&["Termin SF",eur(r.betrag_termin_sf)],+r.betrag_eigenlead>0&&["Eigenlead",eur(r.betrag_eigenlead)],+r.betrag_empfehlung>0&&["Empfehlung",eur(r.betrag_empfehlung)],+r.betrag_speicher>0&&["Speicher",eur(r.betrag_speicher)]].filter(Boolean).map(([k,v])=>(
{k}
{v}
))}
)})}
):null}
{/* ═══ VORSCHUSS MODAL ══════════════════════════════════════ */} {showVSModal&&(
setShowVS(false)}>
e.stopPropagation()}>
Vorschuss erfassen
Abzug von der Provision
{vorschuesse.length>0&&
{vorschuesse.map(v=>{const ma=mitarb.find(m=>m.id===v.mitarbeiter_id);return(
{QICD[v.quelle]||"✋"}
{ma?ma.vorname+" "+ma.nachname:"?"}
{v.grund||v.quelle}
{eur(v.betrag)}
)})}
}
Mitarbeiter *
Betrag €
setVSF(p=>({...p,betrag:e.target.value}))} placeholder="200" className="inp"/>
Quelle
Grund
setVSF(p=>({...p,grund:e.target.value}))} placeholder="Grund" className="inp"/>
)} {/* ═══ SETTINGS MODAL ═══════════════════════════════════════ */} {showSettings&&(
setShowSt(false)}>
e.stopPropagation()}>
⚙️ Einstellungen
🔔 Slack
Automatische Benachrichtigungen
setSlackWH(e.target.value)} placeholder="https://hooks.slack.com/services/..." className="inp" style={{flex:1,fontSize:11}}/>
ℹ️ Status
{[["Bezugsmonat",MOL[bM]+" "+bJ],["Aufträge",rel.length],["Abzüge aktiv",abzuege.filter(a=>abzugAktivInMonat(a,bM,bJ)).length],["Vorschüsse",vorschuesse.length],["Kranktage",kranktage.filter(k=>k.tage>0).length]].map(([k,v])=>)}
)}
) } ReactDOM.createRoot(document.getElementById("root")).render();