Frist part of config. Added standard kpi config data. Added integrating from back- and frontend.
parent
5d5bc986cf
commit
59c918cdce
|
|
@ -34,6 +34,8 @@ def create_kpi_setting():
|
|||
"type",
|
||||
"translation",
|
||||
"example",
|
||||
"position",
|
||||
"active"
|
||||
]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
|
|
@ -58,6 +60,8 @@ def create_kpi_setting():
|
|||
type=kpi_type,
|
||||
translation=data["translation"],
|
||||
example=data["example"],
|
||||
position=data["position"],
|
||||
active=data["active"]
|
||||
)
|
||||
|
||||
db.session.add(new_kpi_setting)
|
||||
|
|
|
|||
|
|
@ -13,3 +13,5 @@ def init_db(app):
|
|||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
from model.seed_data import seed_default_kpi_settings
|
||||
seed_default_kpi_settings()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class KPISettingType(Enum):
|
|||
RANGE = "range"
|
||||
BOOLEAN = "boolean"
|
||||
ARRAY = "array"
|
||||
DATE = "date"
|
||||
|
||||
|
||||
class KPISettingModel(db.Model):
|
||||
|
|
@ -24,6 +25,8 @@ class KPISettingModel(db.Model):
|
|||
)
|
||||
translation: Mapped[str]
|
||||
example: Mapped[str]
|
||||
position: Mapped[int]
|
||||
active: Mapped[bool]
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
|
|
@ -34,12 +37,16 @@ class KPISettingModel(db.Model):
|
|||
"type": self.type.value,
|
||||
"translation": self.translation,
|
||||
"example": self.example,
|
||||
"position": self.position,
|
||||
"active": self.active
|
||||
}
|
||||
|
||||
def __init__(self, name, description, mandatory, type, translation, example):
|
||||
def __init__(self, name, description, mandatory, type, translation, example, position, active):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.mandatory = mandatory
|
||||
self.type = type
|
||||
self.translation = translation
|
||||
self.example = example
|
||||
self.position = position
|
||||
self.active = active
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
from model.database import db
|
||||
from model.kpi_setting_model import KPISettingModel, KPISettingType
|
||||
|
||||
def seed_default_kpi_settings():
|
||||
if KPISettingModel.query.first() is not None:
|
||||
print("KPI Settings bereits vorhanden, Seeding übersprungen")
|
||||
return
|
||||
|
||||
default_kpi_settings = [
|
||||
{
|
||||
"name": "Fondsname",
|
||||
"description": "Der vollständige Name des Investmentfonds",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.STRING,
|
||||
"translation": "Fund Name",
|
||||
"example": "Alpha Real Estate Fund I",
|
||||
"position": 1,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Fondsmanager",
|
||||
"description": "Verantwortlicher Manager für die Fondsverwaltung",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.STRING,
|
||||
"translation": "Fund Manager",
|
||||
"example": "Max Mustermann",
|
||||
"position": 2,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "AIFM",
|
||||
"description": "Alternative Investment Fund Manager",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.STRING,
|
||||
"translation": "AIFM",
|
||||
"example": "Alpha Investment Management GmbH",
|
||||
"position": 3,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Datum",
|
||||
"description": "Stichtag der Datenerfassung",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.DATE,
|
||||
"translation": "Date",
|
||||
"example": "05.05.2025",
|
||||
"position": 4,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Risikoprofil",
|
||||
"description": "Klassifizierung des Risikos des Fonds",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.STRING,
|
||||
"translation": "Risk Profile",
|
||||
"example": "Core/Core++",
|
||||
"position": 5,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Artikel",
|
||||
"description": "Artikel 8 SFDR-Klassifizierung",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.BOOLEAN,
|
||||
"translation": "Article",
|
||||
"example": "Ja",
|
||||
"position": 6,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Zielrendite",
|
||||
"description": "Angestrebte jährliche Rendite in Prozent",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.NUMBER,
|
||||
"translation": "Target Return",
|
||||
"example": "6.5",
|
||||
"position": 7,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Rendite",
|
||||
"description": "Tatsächlich erzielte Rendite in Prozent",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.NUMBER,
|
||||
"translation": "Return",
|
||||
"example": "5.8",
|
||||
"position": 8,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Zielausschüttung",
|
||||
"description": "Geplante Ausschüttung in Prozent",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.NUMBER,
|
||||
"translation": "Target Distribution",
|
||||
"example": "4.0",
|
||||
"position": 9,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Ausschüttung",
|
||||
"description": "Tatsächliche Ausschüttung in Prozent",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.NUMBER,
|
||||
"translation": "Distribution",
|
||||
"example": "3.8",
|
||||
"position": 10,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Laufzeit",
|
||||
"description": "Geplante Laufzeit des Fonds",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.STRING,
|
||||
"translation": "Duration",
|
||||
"example": "7 Jahre",
|
||||
"position": 11,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "LTV",
|
||||
"description": "Loan-to-Value Verhältnis in Prozent",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.NUMBER,
|
||||
"translation": "LTV",
|
||||
"example": "65.0",
|
||||
"position": 12,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Managementgebühren",
|
||||
"description": "Jährliche Verwaltungsgebühren in Prozent",
|
||||
"mandatory": True,
|
||||
"type": KPISettingType.NUMBER,
|
||||
"translation": "Management Fees",
|
||||
"example": "1.5",
|
||||
"position": 13,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Sektorenallokation",
|
||||
"description": "Verteilung der Investments nach Sektoren",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.ARRAY,
|
||||
"translation": "Sector Allocation",
|
||||
"example": "Büroimmobilien, Einzelhandel, Logistik",
|
||||
"position": 14,
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"name": "Länderallokation",
|
||||
"description": "Geografische Verteilung der Investments",
|
||||
"mandatory": False,
|
||||
"type": KPISettingType.ARRAY,
|
||||
"translation": "Country Allocation",
|
||||
"example": "Deutschland, Österreich, Schweiz",
|
||||
"position": 15,
|
||||
"active": True
|
||||
}
|
||||
]
|
||||
|
||||
print("Füge Standard KPI Settings hinzu...")
|
||||
|
||||
for kpi_data in default_kpi_settings:
|
||||
kpi_setting = KPISettingModel(
|
||||
name=kpi_data["name"],
|
||||
description=kpi_data["description"],
|
||||
mandatory=kpi_data["mandatory"],
|
||||
type=kpi_data["type"],
|
||||
translation=kpi_data["translation"],
|
||||
example=kpi_data["example"],
|
||||
position=kpi_data["position"],
|
||||
active=kpi_data["active"]
|
||||
)
|
||||
|
||||
db.session.add(kpi_setting)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
print(f"Erfolgreich {len(default_kpi_settings)} Standard KPI Settings hinzugefügt")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Fehler beim Hinzufügen der Standard KPI Settings: {e}")
|
||||
raise
|
||||
|
|
@ -16,4 +16,6 @@ RUN python -m spacy download en_core_web_sm
|
|||
|
||||
COPY .. /app
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
CMD ["python3.12", "app.py"]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||
import { Box, Button, IconButton, Typography, CircularProgress } from "@mui/material";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const Route = createFileRoute("/config")({
|
||||
component: ConfigPage,
|
||||
|
|
@ -12,39 +12,83 @@ export const Route = createFileRoute("/config")({
|
|||
interface Kennzahl {
|
||||
id: number;
|
||||
name: string;
|
||||
format: string;
|
||||
description: string;
|
||||
mandatory: boolean;
|
||||
type: string;
|
||||
translation: string;
|
||||
example: string;
|
||||
position: number;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
const mockKennzahlen: Kennzahl[] = [
|
||||
{ id: 1, name: "Fondsname", format: "Text", active: true },
|
||||
{ id: 2, name: "Fondsmanager", format: "Text", active: true },
|
||||
{ id: 3, name: "AIFM", format: "Text", active: false },
|
||||
{ id: 4, name: "Datum", format: "Datum", active: true },
|
||||
{ id: 5, name: "Risikoprofil", format: "Text", active: true },
|
||||
{ id: 6, name: "Artikel", format: "Ja/Nein", active: true },
|
||||
{ id: 7, name: "Zielrendite", format: "Zahl", active: true },
|
||||
{ id: 8, name: "Rendite", format: "Zahl", active: true },
|
||||
{ id: 9, name: "Zielausschüttung", format: "Zahl", active: true },
|
||||
{ id: 10, name: "Ausschüttung", format: "Zahl", active: true },
|
||||
{ id: 11, name: "Laufzeit", format: "Text", active: true },
|
||||
{ id: 12, name: "LTV", format: "Zahl", active: true },
|
||||
{ id: 13, name: "Managementgebühren", format: "Zahl", active: true },
|
||||
{ id: 14, name: "Sektorenallokation", format: "Liste (mehrfach)", active: true },
|
||||
{ id: 15, name: "Länderallokation", format: "Liste (mehrfach)", active: true },
|
||||
];
|
||||
const typeDisplayMapping: Record<string, string> = {
|
||||
"string": "Text",
|
||||
"date": "Datum",
|
||||
"boolean": "Ja/Nein",
|
||||
"number": "Zahl",
|
||||
"array": "Liste (mehrfach)"
|
||||
};
|
||||
|
||||
const getDisplayType = (backendType: string): string => {
|
||||
return typeDisplayMapping[backendType] || backendType;
|
||||
};
|
||||
|
||||
function ConfigPage() {
|
||||
const navigate = useNavigate();
|
||||
const [kennzahlen, setKennzahlen] = useState<Kennzahl[]>(mockKennzahlen);
|
||||
const [kennzahlen, setKennzahlen] = useState<Kennzahl[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [draggedItem, setDraggedItem] = useState<Kennzahl | null>(null);
|
||||
|
||||
const handleToggleActive = (id: number) => {
|
||||
setKennzahlen(prev =>
|
||||
prev.map(item =>
|
||||
item.id === id ? { ...item, active: !item.active } : item
|
||||
)
|
||||
);
|
||||
useEffect(() => {
|
||||
const fetchKennzahlen = async () => {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:5050/api/kpi_setting/`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const sortedData = data.sort((a: Kennzahl, b: Kennzahl) => a.position - b.position);
|
||||
setKennzahlen(sortedData);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unbekannter Fehler beim Laden der Daten');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchKennzahlen();
|
||||
}, []);
|
||||
|
||||
const handleToggleActive = async (id: number) => {
|
||||
const kennzahl = kennzahlen.find(k => k.id === id);
|
||||
if (!kennzahl) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:5050/api/kpi_setting/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...kennzahl,
|
||||
active: !kennzahl.active
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
setKennzahlen(prev =>
|
||||
prev.map(item =>
|
||||
item.id === id ? { ...item, active: !item.active } : item
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Aktualisieren der Kennzahl:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragStart = (e: React.DragEvent<HTMLTableRowElement>, item: Kennzahl) => {
|
||||
|
|
@ -76,6 +120,62 @@ function ConfigPage() {
|
|||
setDraggedItem(null);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box
|
||||
minHeight="100vh"
|
||||
width="100vw"
|
||||
bgcolor="white"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
>
|
||||
<CircularProgress sx={{ color: '#383838', mb: 2 }} />
|
||||
<Typography>Lade Kennzahlen...</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Box
|
||||
minHeight="100vh"
|
||||
width="100vw"
|
||||
bgcolor="white"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Typography color="error" mb={2} variant="h6">
|
||||
Fehler beim Laden der Daten
|
||||
</Typography>
|
||||
<Typography color="text.secondary" mb={3} textAlign="center" maxWidth="500px">
|
||||
{error}
|
||||
</Typography>
|
||||
<Box display="flex" gap={2}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => navigate({ to: "/" })}
|
||||
>
|
||||
Zurück
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "#383838",
|
||||
"&:hover": { backgroundColor: "#2e2e2e" },
|
||||
}}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Neu laden
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
minHeight="100vh"
|
||||
|
|
@ -233,7 +333,7 @@ function ConfigPage() {
|
|||
border: "1px solid #ddd",
|
||||
backgroundColor: "#f8f9fa"
|
||||
}}>
|
||||
{kennzahl.format}
|
||||
{getDisplayType(kennzahl.type)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
Loading…
Reference in New Issue