Termin aus Mail, Rollen & Rechte, Abwesenheiten, Glas-Darstellung und Pi-Betrieb
- Termin aus Mail: Mailtext oder .eml per Drag & Drop/Strg+V auf Terminplan oder Aufgaben, KI schlägt Aufgaben vor, Prüfung im Aufgaben-Panel; neues Notizfeld je Aufgabe (plan_todos.notiz) - Terminplan: Meilensteine ohne Aufgabenbezug je in eigener Zeile, "+ Neu"-Menü klappt neben schmaler Seitenleiste nach rechts, Leistungsphasen - Rollen & Rechte, Abwesenheiten-Seite, Benachrichtigungen, Rundgang - Glas-Darstellung, Logo als Favicon - Pi-Betrieb: PDF-Arbeit im Kindprozess, Wächter, Einrichtungs- und Prüfskript inkl. WLAN-Wächter Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f4fb3c550b
commit
b7e0bd0d92
56 files changed
+12265
-2974
No files matched your search
+39
-91
@@ -9,7 +9,7 @@
|
||||
// Die Schritte:
|
||||
//
|
||||
// 1. PROJEKT Nummer, Bezeichnung, GEWERKE, Gruppe, Schlagwörter
|
||||
// 2. AUFTRAG Auftragsstatus, Honorar ODER Projektgröße, beauftragte
|
||||
// 2. AUFTRAG Auftragsstatus, Honorar (freiwillig), beauftragte
|
||||
// Leistungsphasen
|
||||
// 3. STAND amtierende Leistungsphase
|
||||
// 4. TEAM Beteiligte je Rolle — eingeschränkt auf die Gewerke des
|
||||
@@ -20,10 +20,10 @@
|
||||
//
|
||||
// Warum ein Assistent: Honorar, Team und Schlagwörter mussten zuvor NACH dem
|
||||
// Anlegen an drei weiteren Stellen nachgetragen werden — und wurden es meist
|
||||
// nicht. Genau daher rühren die Projekte, die in der Auslastung als "nicht
|
||||
// bewertbar" erscheinen.
|
||||
// nicht.
|
||||
//
|
||||
// JEDER SCHRITT IST PFLICHT: "Weiter" bleibt gesperrt, solange etwas fehlt,
|
||||
// Pflichtangaben sperren "Weiter", solange etwas fehlt (das Honorar ist
|
||||
// freiwillig, Eriks Vorgabe 10/2026),
|
||||
// und unter dem Knopf steht, was fehlt. Ein Assistent, der unvollständige
|
||||
// Projekte durchlässt, löst das Problem nicht, das er lösen soll.
|
||||
//
|
||||
@@ -64,7 +64,7 @@ function openNewProjWizard(){
|
||||
schritt: 0,
|
||||
fertig: null, // angelegtes Projekt, solange der Abschluss steht
|
||||
nr: '', name: '', gewerke: [], gruppe: 'Aktive Projekte', tags: [],
|
||||
status: AUFTRAGSSTATUS_VORGABE, grundlage: 'honorar', honorar: '', groesse: '',
|
||||
status: AUFTRAGSSTATUS_VORGABE, honorar: '',
|
||||
beauftragt: [], amtierend: '',
|
||||
team: {}, fremde: [],
|
||||
planErzeugen: true, planStart: heuteIso(), planPersonen: '',
|
||||
@@ -133,24 +133,12 @@ function wizardHonorar(){
|
||||
const n = parseFloat(String(wizard.honorar).replace(',', '.'));
|
||||
return (!Number.isFinite(n) || n <= 0) ? 0 : n;
|
||||
}
|
||||
function wizardGroesse(){
|
||||
const n = parseFloat(String(wizard.groesse).replace(',', '.'));
|
||||
if(!Number.isFinite(n) || n <= 0) return 0;
|
||||
return Math.min(1, n);
|
||||
}
|
||||
|
||||
// wizardBasis: der Geldbetrag, mit dem gerechnet wird. Bei der Projektgröße
|
||||
// über das firmenweite Referenzhonorar — der Betrag wird bewusst NICHT
|
||||
// angezeigt: Die Größe ist eine Schätzung, und eine daneben stehende
|
||||
// Eurosumme gäbe ihr eine Genauigkeit, die sie nicht hat.
|
||||
// wizardBasis: der Geldbetrag für den Terminplan — allein das Honorar.
|
||||
// Es ist beim Anlegen freiwillig (Eriks Vorgabe); ohne Honorar entsteht
|
||||
// kein Terminplan, er lässt sich später im Projektbereich erzeugen.
|
||||
function wizardBasis(){
|
||||
if(wizard.grundlage === 'honorar'){
|
||||
const h = wizardHonorar();
|
||||
return h > 0 ? { betrag: h, quelle: 'Honorar' } : { betrag: 0, quelle: null };
|
||||
}
|
||||
const g = wizardGroesse();
|
||||
const referenz = Number(APP_SETTINGS.referenzHonorar) || 250000;
|
||||
return g > 0 ? { betrag: g * referenz, quelle: 'Projektgröße' } : { betrag: 0, quelle: null };
|
||||
const h = wizardHonorar();
|
||||
return h > 0 ? { betrag: h, quelle: 'Honorar' } : { betrag: 0, quelle: null };
|
||||
}
|
||||
|
||||
// wizardPlanPersonen: wie viele gleichzeitig an einer Phase arbeiten.
|
||||
@@ -168,7 +156,7 @@ function wizardPlanPersonen(){
|
||||
// aus dem Honorar gerechnet werden kann).
|
||||
function wizardPlanZeilen(){
|
||||
const b = wizardBasis();
|
||||
if(wizard.grundlage !== 'honorar' || b.betrag <= 0) return [];
|
||||
if(b.betrag <= 0) return [];
|
||||
const phasen = wizard.beauftragt.length ? wizard.beauftragt : AUFWAND_LPS;
|
||||
const summe = phasen.reduce((s, l) => s + (Number(HONORAR_ANTEILE[l]) || 0), 0);
|
||||
if(summe <= 0) return [];
|
||||
@@ -202,9 +190,8 @@ function wizardPlanZeilen(){
|
||||
// Klartexten; leer = der Schritt ist vollständig.
|
||||
//
|
||||
// Der Assistent lässt niemanden weiter, solange hier etwas steht: Genau die
|
||||
// Lücken, die sonst entstehen (kein Honorar, keine Phase, niemand
|
||||
// zugeordnet), sind der Grund, warum Projekte später in der Auslastung nicht
|
||||
// bewertbar sind.
|
||||
// Lücken, die sonst entstehen (keine Phase, niemand zugeordnet), fielen
|
||||
// später im Wochenbericht und im Projektbereich auf.
|
||||
function wizardFehlt(key){
|
||||
const fehlt = [];
|
||||
if(key === 'projekt'){
|
||||
@@ -213,8 +200,6 @@ function wizardFehlt(key){
|
||||
if(!wizard.gewerke.length) fehlt.push('mindestens ein Gewerk');
|
||||
}
|
||||
if(key === 'auftrag'){
|
||||
if(wizard.grundlage === 'honorar' && wizardHonorar() <= 0) fehlt.push('Honorar');
|
||||
if(wizard.grundlage === 'groesse' && wizardGroesse() <= 0) fehlt.push('Projektgröße');
|
||||
if(!wizard.beauftragt.length) fehlt.push('mindestens eine beauftragte Leistungsphase');
|
||||
}
|
||||
if(key === 'stand'){
|
||||
@@ -224,7 +209,7 @@ function wizardFehlt(key){
|
||||
if(wizardTeamAnzahl() === 0) fehlt.push('mindestens eine beteiligte Person');
|
||||
}
|
||||
if(key === 'terminplan'){
|
||||
if(wizard.grundlage === 'honorar' && wizard.planErzeugen && !wizard.planStart) fehlt.push('Beginn');
|
||||
if(wizardHonorar() > 0 && wizard.planErzeugen && !wizard.planStart) fehlt.push('Beginn');
|
||||
}
|
||||
return fehlt;
|
||||
}
|
||||
@@ -317,35 +302,19 @@ function wizardProjektHtml(){
|
||||
}
|
||||
|
||||
function wizardAuftragHtml(){
|
||||
const perHonorar = wizard.grundlage === 'honorar';
|
||||
return `
|
||||
<div class="form-row">
|
||||
<label>Auftragsstatus</label>
|
||||
<select onchange="wizardSetz('status', this.value)">
|
||||
${auftragsstatusOptionsHtml(wizard.status)}
|
||||
</select>
|
||||
<span class="hint">Anfrage und Angebot wandern in die Gruppe „In Anbahnung" und zählen nicht zur Auslastung.</span>
|
||||
<span class="hint">Anfrage und Angebot wandern in die Gruppe „In Anbahnung".</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Grundlage der Rechnung</label>
|
||||
<div class="wiz-chips">
|
||||
<button type="button" class="wiz-chip" aria-pressed="${perHonorar}" onclick="wizardSetz('grundlage','honorar')">Vereinbartes Honorar</button>
|
||||
<button type="button" class="wiz-chip" aria-pressed="${!perHonorar}" onclick="wizardSetz('grundlage','groesse')">Geschätzte Projektgröße</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wiz-zeile">
|
||||
<div class="form-row">
|
||||
<label>Honorar (netto, €) ${perHonorar ? '<span class="wiz-pflicht">*</span>' : ''}</label>
|
||||
<input type="number" id="wizHonorar" min="0" step="1000" value="${esc(wizard.honorar)}" placeholder="z. B. 480000"
|
||||
${perHonorar ? `oninput="wizardSetz('honorar', this.value)"` : 'disabled'}>
|
||||
<span class="hint" id="wizHinweisHonorar">${wizardHinweisHonorar()}</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Projektgröße (0,00 – 1,00) ${!perHonorar ? '<span class="wiz-pflicht">*</span>' : ''}</label>
|
||||
<input type="number" id="wizGroesse" min="0" max="1" step="0.05" value="${esc(wizard.groesse)}" placeholder="z. B. 0,40"
|
||||
${!perHonorar ? `oninput="wizardSetz('groesse', this.value)"` : 'disabled'}>
|
||||
<span class="hint" id="wizHinweisGroesse">${wizardHinweisGroesse()}</span>
|
||||
</div>
|
||||
<label>Honorar (netto, €)</label>
|
||||
<input type="number" id="wizHonorar" min="0" step="1000" value="${esc(wizard.honorar)}" placeholder="z. B. 480000"
|
||||
oninput="wizardSetz('honorar', this.value)">
|
||||
<span class="hint" id="wizHinweisHonorar">${wizardHinweisHonorar()}</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Beauftragte Leistungsphasen <span class="wiz-pflicht">*</span></label>
|
||||
@@ -358,18 +327,13 @@ function wizardAuftragHtml(){
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Die beiden Hinweistexte stehen als eigene Funktionen da, weil sie beim
|
||||
// Tippen mitlaufen müssen, ohne dass das Eingabefeld neu gebaut wird — sonst
|
||||
// springt der Cursor ans Feldende.
|
||||
// Der Hinweistext steht als eigene Funktion da, weil er beim Tippen
|
||||
// mitläuft, ohne dass das Eingabefeld neu gebaut wird — sonst springt der
|
||||
// Cursor ans Feldende.
|
||||
function wizardHinweisHonorar(){
|
||||
return wizard.grundlage === 'honorar'
|
||||
? 'Grundlage für Leistungsstand, Auslastung und Terminplan.'
|
||||
: 'Gesperrt — es wird mit der Projektgröße gerechnet.';
|
||||
}
|
||||
function wizardHinweisGroesse(){
|
||||
return wizard.grundlage === 'groesse'
|
||||
? 'Grober Umfang des Projekts. 0 = keine Angabe, 1 = großes Projekt.'
|
||||
: 'Gesperrt — es wird mit dem Honorar gerechnet.';
|
||||
return wizardHonorar() > 0
|
||||
? 'Grundlage für Leistungsstand und Terminplan.'
|
||||
: 'Freiwillig. Ohne Honorar entsteht kein Terminplan; es lässt sich später in den Projekteinstellungen nachtragen.';
|
||||
}
|
||||
|
||||
function wizardStandHtml(){
|
||||
@@ -382,8 +346,7 @@ function wizardStandHtml(){
|
||||
<button type="button" class="wiz-chip" aria-pressed="${wizard.amtierend === l}"
|
||||
onclick="wizardSetz('amtierend','${esc(l)}')">${esc(l)}</button>`).join('')}
|
||||
</div>
|
||||
<span class="hint">Woran gerade gearbeitet wird. Ohne diese Angabe erschiene das Projekt
|
||||
in der Auslastung als nicht bewertbar.</span>
|
||||
<span class="hint">Woran gerade gearbeitet wird.</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -443,14 +406,11 @@ function wizardTeamHtml(){
|
||||
}
|
||||
|
||||
function wizardTerminplanHtml(){
|
||||
// Aus der Projektgröße lässt sich kein Terminplan rechnen: Sie ist eine
|
||||
// Schätzung des Umfangs, kein vereinbarter Betrag. Termine, die daraus
|
||||
// entstünden, sähen im Gantt so verbindlich aus wie errechnete.
|
||||
if(wizard.grundlage !== 'honorar'){
|
||||
// Ohne Honorar kein Terminplan — er lässt sich später im Projektbereich
|
||||
// erzeugen („Terminplan aus Honorar").
|
||||
if(wizardHonorar() <= 0){
|
||||
return `<div class="wiz-sperre">
|
||||
<b>Kein Terminplan aus der Projektgröße.</b>
|
||||
<p>Die Größe ist eine Schätzung des Umfangs, kein vereinbarter Betrag. Termine, die daraus
|
||||
entstünden, sähen im Terminplan so verbindlich aus wie errechnete — ohne es zu sein.</p>
|
||||
<b>Kein Terminplan ohne Honorar.</b>
|
||||
<p>Sobald das Honorar feststeht, lässt sich der Terminplan im Projektbereich jederzeit
|
||||
nachträglich erzeugen („Terminplan aus Honorar").</p>
|
||||
</div>`;
|
||||
@@ -537,9 +497,7 @@ function wizardPruefenHtml(){
|
||||
${zeile('Gruppe', esc(wizard.gruppe))}
|
||||
${zeile('Schlagwörter', wizard.tags.length ? esc(wizard.tags.join(', ')) : 'keine', !wizard.tags.length)}
|
||||
${zeile('Auftragsstatus', esc(auftragsstatusLabel(wizard.status)))}
|
||||
${zeile('Grundlage', wizard.grundlage === 'honorar'
|
||||
? eurFormat(wizardHonorar()) + ' Honorar'
|
||||
: 'Projektgröße ' + faktorFormat(wizardGroesse()))}
|
||||
${zeile('Honorar', wizardHonorar() > 0 ? eurFormat(wizardHonorar()) : 'nicht angegeben', !(wizardHonorar() > 0))}
|
||||
${zeile('Beauftragte Phasen', esc(wizard.beauftragt.join(', ')))}
|
||||
${zeile('Amtierende Phase', esc(wizard.amtierend))}
|
||||
${zeile('Team', esc(team))}
|
||||
@@ -547,14 +505,12 @@ function wizardPruefenHtml(){
|
||||
const p = wizardPersonen().find(x => x.id === id);
|
||||
return p ? p.kuerzel + (p.gewerk ? ' (' + p.gewerk + ')' : '') : id;
|
||||
}).join(', '))) : ''}
|
||||
${zeile('Terminplan', wizard.grundlage !== 'honorar'
|
||||
? 'nicht möglich — Grundlage ist die Projektgröße'
|
||||
${zeile('Terminplan', !(wizardHonorar() > 0)
|
||||
? 'nicht möglich — kein Honorar angegeben'
|
||||
: (!wizard.planErzeugen ? 'wird nicht erzeugt'
|
||||
: (plan.length ? plan.length + ' Phasen bis ' + formatWeekDate(isoVon(plan[plan.length - 1].ende)) : 'nicht errechenbar')),
|
||||
wizard.grundlage !== 'honorar' || !wizard.planErzeugen || !plan.length)}
|
||||
</div>
|
||||
<p class="hint" style="margin-top:14px">Die Gewichtung des Aufwands je Phase und Rolle übernimmt die
|
||||
firmenweite Vorlage; sie lässt sich später im Projektbereich anpassen.</p>`;
|
||||
!(wizardHonorar() > 0) || !wizard.planErzeugen || !plan.length)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function wizardAbschlussHtml(){
|
||||
@@ -585,13 +541,8 @@ function wizardSetz(feld, wert){
|
||||
if(wert === 'anfrage' || wert === 'angebot') wizard.gruppe = 'In Anbahnung';
|
||||
else if(wizard.gruppe === 'In Anbahnung') wizard.gruppe = 'Aktive Projekte';
|
||||
}
|
||||
if(feld === 'grundlage'){
|
||||
// Das nun gesperrte Feld leeren: Ein Wert, der dasteht und nicht mehr
|
||||
// zählt, führt in die Irre.
|
||||
if(wert === 'honorar') wizard.groesse = ''; else wizard.honorar = '';
|
||||
}
|
||||
// Freitextfelder nicht neu bauen — sonst springt der Cursor ans Feldende.
|
||||
if(feld === 'nr' || feld === 'name' || feld === 'honorar' || feld === 'groesse' || feld === 'planPersonen'){
|
||||
if(feld === 'nr' || feld === 'name' || feld === 'honorar' || feld === 'planPersonen'){
|
||||
wizardNurAbhaengiges();
|
||||
return;
|
||||
}
|
||||
@@ -624,8 +575,6 @@ function wizardNurAbhaengiges(){
|
||||
|
||||
const hh = document.getElementById('wizHinweisHonorar');
|
||||
if(hh) hh.textContent = wizardHinweisHonorar();
|
||||
const hg = document.getElementById('wizHinweisGroesse');
|
||||
if(hg) hg.textContent = wizardHinweisGroesse();
|
||||
|
||||
const tab = document.getElementById('wizPlanTabelle');
|
||||
if(tab) tab.innerHTML = wizardPlanTabelleHtml();
|
||||
@@ -725,8 +674,7 @@ function wizardAnlegen(){
|
||||
status: '', statusState: undefined, order: nextOrder,
|
||||
lph: {}, assign: {}, plan: null,
|
||||
auftragsstatus: auftragsstatusOf({ auftragsstatus: wizard.status }),
|
||||
honorar: wizard.grundlage === 'honorar' ? wizardHonorar() : 0,
|
||||
groesse: wizard.grundlage === 'groesse' ? wizardGroesse() : 0,
|
||||
honorar: wizardHonorar(),
|
||||
beauftragteLP: wizard.beauftragt.slice(),
|
||||
aktuelleLP: [wizard.amtierend],
|
||||
};
|
||||
@@ -744,13 +692,13 @@ function wizardAnlegen(){
|
||||
todos: zeilen.map((z, i) => ({
|
||||
id: uid(),
|
||||
title: z.lph,
|
||||
lph: z.lph,
|
||||
start: isoVon(z.start),
|
||||
end: isoVon(z.ende),
|
||||
done: false,
|
||||
progress: 0,
|
||||
assignees: [],
|
||||
deps: [],
|
||||
order: i,
|
||||
dependsOn: [],
|
||||
})),
|
||||
milestones: [],
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user