feat(projekt): Projektmanagement-Tiefe nach Buch & Leitfaden v3.0
ci / quality (push) Failing after 1m0s
ci / build (push) Has been skipped

Operationalisiert den ToolCTRL-Projektmanagement-Leitfaden auf der
Projekt-Detailseite (hybrid: Cockpit-Primitive nativ, Schwergewichtiges
per Outline-Link):

- Projekt-Modus (Relax/Standard/Eskalation/Taskforce) + Dashboard-Surfacing
- Risiko- & Chancen-Register (Wahrscheinlichkeit×Auswirkung, Showstopper,
  Owner, Strategie) — offene Showstopper landen auf dem Dashboard
- Stakeholder-Analyse mit Macht/Interesse-Matrix
- Ziel & Definition: SMART-Ziel, Nicht-Ziele, Liefergegenstaende, Maxime,
  Zieldreieck (welche Ecke gibt nach)
- Meilensteine editierbar inkl. Ergebnis/Kundennutzen, Freitags-/
  Feiertags-Warnung, Mindest-2-Hinweis
- Phasen-Lebenszyklus (Definition→Planung→Umsetzung→Abschluss) mit Gate-Reviews
- Projekt-Statusbericht (Ampel/Trend/Text, extern-Flag, Verlauf) — speist
  statusCache & Pflicht-Minimum
- Änderungs-Log (Scope-Creep-Schutz), strukturierter Abschluss mit
  Checkliste + Abschlussvermerk

Migrationen 0009_pm_insights + 0010_leitfaden_phases. Enthält auch die
gemeinsame schema.ts (inkl. Lead-Tabellen aus dem Vor-Commit) und Seed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 4.8 committed 2026-06-05 17:31:30 +02:00
1 parent d72aa9fbf3
commit 864a1cef6a
22 files changed
+9237 -60

No files matched your search

+5 -1
View File
@@ -4,5 +4,9 @@ export * as ProjectSchemas from './schemas/project.js';
export * as DealSchemas from './schemas/deal.js';
export * as CampaignSchemas from './schemas/campaign.js';
export * as TimeEntrySchemas from './schemas/time-entry.js';
export * as LeadSchemas from './schemas/lead.js';
export * as RiskSchemas from './schemas/risk.js';
export * as StakeholderSchemas from './schemas/stakeholder.js';
export * as ChangeSchemas from './schemas/change.js';
export const SHARED_VERSION = '0.0.3';
export const SHARED_VERSION = '0.0.6';
+24
View File
@@ -0,0 +1,24 @@
import { z } from 'zod';
export const changeEntscheidungSchema = z.enum(['offen', 'genehmigt', 'abgelehnt']);
export const CHANGE_ENTSCHEIDUNGEN = changeEntscheidungSchema.options;
export const changeForProjectInput = z.object({ projectId: z.string().uuid() });
export const createChangeInput = z.object({
projectId: z.string().uuid(),
titel: z.string().min(1).max(300),
auswirkung: z.string().max(1000).optional(),
entscheidung: changeEntscheidungSchema.default('offen'),
});
export type CreateChangeInput = z.infer<typeof createChangeInput>;
export const updateChangeInput = z.object({
id: z.string().uuid(),
titel: z.string().min(1).max(300).optional(),
auswirkung: z.string().max(1000).nullable().optional(),
entscheidung: changeEntscheidungSchema.optional(),
});
export type UpdateChangeInput = z.infer<typeof updateChangeInput>;
export const removeChangeInput = z.object({ id: z.string().uuid() });
+90
View File
@@ -1,5 +1,8 @@
import { z } from 'zod';
const ampelSchema = z.enum(['green', 'amber', 'red']);
const trendSchema = z.enum(['besser', 'gleich', 'schlechter']);
export const createProjectInput = z.object({
areaId: z.string().uuid(),
name: z.string().min(1).max(200),
@@ -36,11 +39,98 @@ export type CreateProjectInput = z.infer<typeof createProjectInput>;
export const archiveProjectInput = z.object({
projectId: z.string().uuid(),
lessonsLearnedUrl: z.string().url(),
abschlussvermerk: z.string().max(2000).optional(),
});
export type ArchiveProjectInput = z.infer<typeof archiveProjectInput>;
// --- Zieldreieck (Leitfaden Kap. 1) ----------------------------------------
export const zieldreieckFlexSchema = z.enum(['leistung', 'zeit', 'aufwand']);
// --- Projektphasen + Gate-Review (Leitfaden Kap. 2 + 5) --------------------
export const projectPhaseSchema = z.enum(['definition', 'planung', 'umsetzung', 'abschluss']);
export const PROJECT_PHASES = projectPhaseSchema.options;
export const setPhaseInput = z.object({
projectId: z.string().uuid(),
phase: projectPhaseSchema,
});
export type SetPhaseInput = z.infer<typeof setPhaseInput>;
export const addPhaseGateInput = z.object({
projectId: z.string().uuid(),
phase: projectPhaseSchema,
ergebnisErreicht: z.boolean(),
notiz: z.string().max(1000).optional(),
advance: z.boolean().default(false), // Folgephase direkt freigeben
});
export type AddPhaseGateInput = z.infer<typeof addPhaseGateInput>;
export const phaseGatesInput = z.object({ projectId: z.string().uuid() });
// --- Projekt-Statusbericht (Leitfaden Kap. 5) ------------------------------
export const addProjectStatusInput = z.object({
projectId: z.string().uuid(),
ampel: ampelSchema,
trend: trendSchema.optional(),
text: z.string().max(1000).optional(),
externSichtbar: z.boolean().default(false),
});
export type AddProjectStatusInput = z.infer<typeof addProjectStatusInput>;
export const projectStatusHistoryInput = z.object({ projectId: z.string().uuid() });
export const setMilestoneStatusInput = z.object({
milestoneId: z.string().uuid(),
status: z.enum(['open', 'done', 'overdue']),
});
export type SetMilestoneStatusInput = z.infer<typeof setMilestoneStatusInput>;
// --- Projekt-Modus (Buch Kap. 4 Strategie) ---------------------------------
export const projectModusSchema = z.enum(['relax', 'standard', 'eskalation', 'taskforce']);
export const PROJECT_MODI = projectModusSchema.options;
export const setModusInput = z.object({
projectId: z.string().uuid(),
modus: projectModusSchema,
});
export type SetModusInput = z.infer<typeof setModusInput>;
// --- Ziel-Disziplin: Maxime / Beschreibung / DoD editierbar ----------------
export const updateProjectInput = z.object({
projectId: z.string().uuid(),
maxime: z.string().max(280).nullable().optional(),
description: z.string().max(2000).nullable().optional(),
dodText: z.string().max(2000).nullable().optional(),
ziel: z.string().max(1000).nullable().optional(),
nichtZiele: z.string().max(2000).nullable().optional(),
liefergegenstaende: z.string().max(2000).nullable().optional(),
zieldreieckFlex: zieldreieckFlexSchema.nullable().optional(),
});
export type UpdateProjectInput = z.infer<typeof updateProjectInput>;
// --- Meilenstein-Disziplin: anlegen, editieren, entfernen ------------------
export const addMilestoneInput = z.object({
projectId: z.string().uuid(),
title: z.string().min(1).max(200),
due: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/)
.optional(),
});
export type AddMilestoneInput = z.infer<typeof addMilestoneInput>;
export const updateMilestoneInput = z.object({
milestoneId: z.string().uuid(),
title: z.string().min(1).max(200).optional(),
due: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/)
.nullable()
.optional(),
ergebnis: z.string().max(2000).nullable().optional(),
kundennutzen: z.string().max(1000).nullable().optional(),
status: z.enum(['open', 'done', 'overdue']).optional(),
});
export type UpdateMilestoneInput = z.infer<typeof updateMilestoneInput>;
export const removeMilestoneInput = z.object({ milestoneId: z.string().uuid() });
export type RemoveMilestoneInput = z.infer<typeof removeMilestoneInput>;
+43
View File
@@ -0,0 +1,43 @@
import { z } from 'zod';
export const riskKindSchema = z.enum(['risiko', 'chance']);
export const riskLevelSchema = z.enum(['niedrig', 'mittel', 'hoch']);
export const riskHerkunftSchema = z.enum(['intern', 'extern']);
export const riskStatusSchema = z.enum(['offen', 'beobachtet', 'eingetreten', 'geschlossen']);
export const RISK_STATUSES = riskStatusSchema.options;
export const riskStrategieSchema = z.enum(['vermeiden', 'verlagern', 'mindern', 'akzeptieren']);
export const RISK_STRATEGIEN = riskStrategieSchema.options;
export const riskForProjectInput = z.object({ projectId: z.string().uuid() });
export const createRiskInput = z.object({
projectId: z.string().uuid(),
kind: riskKindSchema.default('risiko'),
title: z.string().min(1).max(500),
wahrscheinlichkeit: riskLevelSchema.default('mittel'),
auswirkung: riskLevelSchema.default('mittel'),
herkunft: riskHerkunftSchema.optional(),
showstopper: z.boolean().default(false),
massnahme: z.string().max(1000).optional(),
strategie: riskStrategieSchema.optional(),
ownerUserId: z.string().uuid().optional(),
status: riskStatusSchema.default('offen'),
});
export type CreateRiskInput = z.infer<typeof createRiskInput>;
export const updateRiskInput = z.object({
id: z.string().uuid(),
kind: riskKindSchema.optional(),
title: z.string().min(1).max(500).optional(),
wahrscheinlichkeit: riskLevelSchema.optional(),
auswirkung: riskLevelSchema.optional(),
herkunft: riskHerkunftSchema.nullable().optional(),
showstopper: z.boolean().optional(),
massnahme: z.string().max(1000).nullable().optional(),
strategie: riskStrategieSchema.nullable().optional(),
ownerUserId: z.string().uuid().nullable().optional(),
status: riskStatusSchema.optional(),
});
export type UpdateRiskInput = z.infer<typeof updateRiskInput>;
export const removeRiskInput = z.object({ id: z.string().uuid() });
@@ -0,0 +1,32 @@
import { z } from 'zod';
export const stakeholderLevelSchema = z.enum(['niedrig', 'mittel', 'hoch']);
export const stakeholderHaltungSchema = z.enum(['pro', 'neutral', 'contra']);
export const stakeholderForProjectInput = z.object({ projectId: z.string().uuid() });
export const createStakeholderInput = z.object({
projectId: z.string().uuid(),
name: z.string().min(1).max(200),
rolle: z.string().max(200).optional(),
interesse: stakeholderLevelSchema.default('mittel'),
einfluss: stakeholderLevelSchema.default('mittel'),
haltung: stakeholderHaltungSchema.default('neutral'),
beduerfnis: z.string().max(1000).optional(),
notiz: z.string().max(1000).optional(),
});
export type CreateStakeholderInput = z.infer<typeof createStakeholderInput>;
export const updateStakeholderInput = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(200).optional(),
rolle: z.string().max(200).nullable().optional(),
interesse: stakeholderLevelSchema.optional(),
einfluss: stakeholderLevelSchema.optional(),
haltung: stakeholderHaltungSchema.optional(),
beduerfnis: z.string().max(1000).nullable().optional(),
notiz: z.string().max(1000).nullable().optional(),
});
export type UpdateStakeholderInput = z.infer<typeof updateStakeholderInput>;
export const removeStakeholderInput = z.object({ id: z.string().uuid() });