160 lines
6.4 KiB
Python
160 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Phase-20: slack-stub, github-link, time-budget, budget-alerts, recurring-entries."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import datetime
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from phase2_features import Feature, FileGen, ROOT, log, log_section # noqa: E402
|
|
from phase3_features import run_feature_v2 # noqa: E402
|
|
|
|
PHASE20_STATE = ROOT / ".phase20-state.json"
|
|
|
|
FEATURES: list[Feature] = [
|
|
Feature(
|
|
name="time-budget-per-project",
|
|
description="Budget-Feld (Stunden) pro Project + Anzeige used/total",
|
|
files=[
|
|
FileGen(
|
|
path="apps/api/src/db/schema.ts",
|
|
purpose=(
|
|
"WICHTIG: BEHALTE alle existierenden Tabellen — füge nur Spalte hinzu. "
|
|
"Konkret: füge `budgetHours: integer('budget_hours')` (nullable) auf projects-Tabelle. "
|
|
"BEHALTE explizit: users, customers, projects, projectTemplates, timeEntries, timeEntryAttachments, timeEntryComments, "
|
|
"appSettings, auditLog, documents, webhooks, savedViews, apiKeys, passwordResetTokens, invitations."
|
|
),
|
|
refs=["apps/api/src/db/schema.ts"],
|
|
),
|
|
FileGen(
|
|
path="apps/web/src/pages/Projects.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte Create-Form. Füge Budget-Spalte: zeigt used/total Stunden mit Progress-Bar pro Project. "
|
|
"Bei >100% rot. Edit-Modal mit Budget-Input. Verwende api.getProjectStats für hours."
|
|
),
|
|
refs=["apps/web/src/pages/Projects.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="recurring-time-entries",
|
|
description="Template-System für wiederkehrende Entries (z.B. Daily-Standup 30min)",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/pages/TimeEntries.tsx",
|
|
purpose=(
|
|
"ERWEITERT — füge 'Aus Template' Dropdown im Create-Form, lädt api.listTimeEntryTemplates(), "
|
|
"Auswahl pre-fillt description/projectId/durationMinutes (durationMinutes ergibt now/now+duration)."
|
|
),
|
|
refs=["apps/web/src/pages/TimeEntries.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="slack-integration-stub",
|
|
description="Slack-Integration-Stub Card auf Integrations-Page",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/pages/Integrations.tsx",
|
|
purpose=(
|
|
"ERWEITERT — die bestehende Slack-Karte bekommt jetzt 'Configure'-Button (statt 'Coming Soon'). "
|
|
"Klick öffnet Modal mit Webhook-URL-Input. Submit speichert (würde später als Setting). "
|
|
"Plus: Test-Button → sendet 'Hello from EmberClone' POST zur URL."
|
|
),
|
|
refs=["apps/web/src/pages/Integrations.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="github-link-on-entries",
|
|
description="GitHub-Link-Feld pro TimeEntry (z.B. PR-URL)",
|
|
files=[
|
|
FileGen(
|
|
path="apps/api/src/db/schema.ts",
|
|
purpose=(
|
|
"WICHTIG: BEHALTE alle bestehenden Tabellen. Füge nur Spalte `externalLink: text('external_link')` (nullable) zu timeEntries. "
|
|
"ALLE anderen Tabellen unverändert."
|
|
),
|
|
refs=["apps/api/src/db/schema.ts"],
|
|
),
|
|
FileGen(
|
|
path="apps/web/src/pages/TimeEntries.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte alles. Füge im Create-Form optional 'GitHub/Link'-Input. "
|
|
"In Liste: kleines link-icon wenn externalLink set, hover zeigt URL."
|
|
),
|
|
refs=["apps/web/src/pages/TimeEntries.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="budget-alerts",
|
|
description="Toast-Warning bei Project-Budget >80% und >100%",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/pages/Projects.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte alles. Beim Mount: für jedes Project check budget vs used hours. "
|
|
"Wenn >80% aber ≤100%: useToast().info('Budget für X bei 85%'). >100%: useToast().error('Budget für X überschritten')."
|
|
),
|
|
refs=["apps/web/src/pages/Projects.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="api-client-phase20",
|
|
description="API: pinned-customers + budget-update Endpoints",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/lib/api.ts",
|
|
purpose=(
|
|
"ERWEITERT — behalte ALLES. Füge: updateProjectBudget(id, hours), setExternalLink(entryId, url). "
|
|
"Plus: testSlackWebhook(url) (POST mit json {text:'Hello from EmberClone'})."
|
|
),
|
|
refs=["apps/web/src/lib/api.ts"],
|
|
),
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
def load_state() -> dict:
|
|
if PHASE20_STATE.exists():
|
|
return json.loads(PHASE20_STATE.read_text())
|
|
return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()}
|
|
|
|
|
|
def save_state(state: dict) -> None:
|
|
PHASE20_STATE.write_text(json.dumps(state, indent=2))
|
|
|
|
|
|
async def main() -> int:
|
|
log_section("🚀 Phase-20 Codegen-Run gestartet")
|
|
state = load_state()
|
|
for feature in FEATURES:
|
|
if feature.name in state.get("completed_features", []):
|
|
continue
|
|
state["current_feature"] = feature.name; save_state(state)
|
|
try:
|
|
success = await run_feature_v2(feature)
|
|
if success:
|
|
state.setdefault("completed_features", []).append(feature.name)
|
|
else:
|
|
state.setdefault("attempted_features", []).append(feature.name)
|
|
save_state(state)
|
|
except Exception as e:
|
|
log(f"❌ {feature.name} crashed: {e}", level="ERROR")
|
|
state.setdefault("attempted_features", []).append(feature.name); save_state(state)
|
|
|
|
log_section("Phase-20 Run beendet")
|
|
log(f"OK: {len(state.get('completed_features', []))}, Attempted: {len(state.get('attempted_features', []))}, Total: {len(FEATURES)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(asyncio.run(main()))
|