1573 lines
46 KiB
TypeScript
1573 lines
46 KiB
TypeScript
import { create } from "zustand";
|
|
import { persist, createJSONStorage } from "zustand/middleware";
|
|
import {
|
|
ApplicationDetails,
|
|
ApplicationListItem,
|
|
FormData,
|
|
Variant,
|
|
CreateResponse,
|
|
UpdateResponse,
|
|
RootPayload,
|
|
} from "../types/api";
|
|
import { apiClient, isSuccessResponse, getErrorMessage } from "../api/client";
|
|
|
|
interface ApplicationState {
|
|
// Current application
|
|
currentApplication: ApplicationDetails | null;
|
|
applicationList: ApplicationListItem[];
|
|
|
|
// Authentication
|
|
paId: string | null;
|
|
paKey: string | null;
|
|
masterKey: string | null;
|
|
isAdmin: boolean;
|
|
|
|
// UI State
|
|
isLoading: boolean;
|
|
isSubmitting: boolean;
|
|
error: string | null;
|
|
successMessage: string | null;
|
|
|
|
// Form state
|
|
formData: Partial<FormData>;
|
|
isDirty: boolean;
|
|
|
|
// Pagination
|
|
currentPage: number;
|
|
totalItems: number;
|
|
itemsPerPage: number;
|
|
|
|
// Filters
|
|
statusFilter: string | null;
|
|
variantFilter: string | null;
|
|
searchQuery: string | null;
|
|
|
|
// Sorting
|
|
sortBy: string;
|
|
sortOrder: "asc" | "desc";
|
|
}
|
|
|
|
interface ApplicationActions {
|
|
// Authentication
|
|
setApplicationCredentials: (paId: string, paKey: string) => void;
|
|
setMasterKey: (masterKey: string) => void;
|
|
validateMasterKey: () => Promise<boolean>;
|
|
clearCredentials: () => void;
|
|
|
|
// Application CRUD
|
|
createApplication: (
|
|
pdf?: File,
|
|
formData?: Partial<FormData>,
|
|
variant?: Variant,
|
|
) => Promise<boolean>;
|
|
updateApplication: (
|
|
pdf?: File,
|
|
formData?: Partial<FormData>,
|
|
variant?: Variant,
|
|
) => Promise<boolean>;
|
|
loadApplication: (paId?: string, paKey?: string) => Promise<boolean>;
|
|
deleteApplication: (paId?: string, paKey?: string) => Promise<boolean>;
|
|
downloadApplicationPdf: (paId?: string, paKey?: string) => Promise<boolean>;
|
|
|
|
// Application list management
|
|
loadApplicationList: (refresh?: boolean) => Promise<boolean>;
|
|
searchApplications: (query: string) => Promise<boolean>;
|
|
searchApplicationsAdvanced: (params: any) => Promise<boolean>;
|
|
setStatusFilter: (status: string | null) => void;
|
|
setVariantFilter: (variant: string | null) => void;
|
|
setPage: (page: number) => void;
|
|
setSorting: (sortBy: string, sortOrder: "asc" | "desc") => void;
|
|
|
|
// Admin operations
|
|
loadApplicationAdmin: (paId: string) => Promise<boolean>;
|
|
loadApplicationListAdmin: () => Promise<boolean>;
|
|
updateApplicationStatusAdmin: (
|
|
paId: string,
|
|
status: string,
|
|
) => Promise<boolean>;
|
|
deleteApplicationAdmin: (paId: string) => Promise<boolean>;
|
|
downloadApplicationPdfAdmin: (paId: string) => Promise<boolean>;
|
|
updateApplicationAdmin: (
|
|
paId: string,
|
|
pdf?: File,
|
|
formData?: Partial<FormData>,
|
|
variant?: Variant,
|
|
) => Promise<boolean>;
|
|
resetApplicationCredentials: (
|
|
paId: string,
|
|
) => Promise<{ pa_id: string; pa_key: string } | null>;
|
|
|
|
// Bulk operations
|
|
bulkDeleteApplications: (paIds: string[]) => Promise<boolean>;
|
|
bulkApproveApplications: (paIds: string[]) => Promise<boolean>;
|
|
bulkRejectApplications: (paIds: string[]) => Promise<boolean>;
|
|
bulkSetInReviewApplications: (paIds: string[]) => Promise<boolean>;
|
|
bulkSetNewApplications: (paIds: string[]) => Promise<boolean>;
|
|
|
|
// Form management
|
|
updateFormData: (data: Partial<FormData>) => void;
|
|
resetFormData: () => void;
|
|
setFormData: (data: Partial<FormData>) => void;
|
|
markFormDirty: () => void;
|
|
markFormClean: () => void;
|
|
|
|
// UI state management
|
|
setLoading: (loading: boolean) => void;
|
|
setSubmitting: (submitting: boolean) => void;
|
|
setError: (error: string | null) => void;
|
|
setSuccessMessage: (message: string | null) => void;
|
|
clearMessages: () => void;
|
|
|
|
// Utility
|
|
reset: () => void;
|
|
isCurrentUserApplication: (paId: string) => boolean;
|
|
}
|
|
|
|
const initialFormData: Partial<FormData> = {
|
|
applicantType: "person",
|
|
institutionType: "-",
|
|
institutionName: "",
|
|
firstName: "",
|
|
lastName: "",
|
|
email: "",
|
|
phone: "",
|
|
course: "-",
|
|
role: "-",
|
|
projectName: "",
|
|
startDate: "",
|
|
endDate: "",
|
|
participants: undefined,
|
|
description: "",
|
|
participatingFaculties: {
|
|
inf: false,
|
|
esb: false,
|
|
ls: false,
|
|
tec: false,
|
|
tex: false,
|
|
nxt: false,
|
|
open: false,
|
|
},
|
|
costs: [],
|
|
requestedAmountEur: 0,
|
|
variant: "VSM",
|
|
};
|
|
|
|
const initialState: ApplicationState = {
|
|
currentApplication: null,
|
|
applicationList: [],
|
|
paId: null,
|
|
paKey: null,
|
|
masterKey: null,
|
|
isAdmin: false,
|
|
isLoading: false,
|
|
isSubmitting: false,
|
|
error: null,
|
|
successMessage: null,
|
|
formData: initialFormData,
|
|
isDirty: false,
|
|
currentPage: 0,
|
|
totalItems: 0,
|
|
itemsPerPage: 50,
|
|
statusFilter: null,
|
|
variantFilter: null,
|
|
searchQuery: null,
|
|
|
|
// Sorting
|
|
sortBy: "created_at",
|
|
sortOrder: "desc",
|
|
};
|
|
|
|
export const useApplicationStore = create<
|
|
ApplicationState & ApplicationActions
|
|
>()(
|
|
persist(
|
|
(set, get) => ({
|
|
...initialState,
|
|
|
|
// Authentication
|
|
setApplicationCredentials: (paId: string, paKey: string) => {
|
|
set({ paId, paKey, isAdmin: false });
|
|
apiClient.clearMasterKey();
|
|
},
|
|
|
|
setMasterKey: (masterKey: string) => {
|
|
set({ masterKey, isAdmin: true });
|
|
apiClient.setMasterKey(masterKey);
|
|
},
|
|
|
|
validateMasterKey: async (): Promise<boolean> => {
|
|
const { masterKey } = get();
|
|
if (!masterKey) {
|
|
set({ error: "Master key is required" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
// Try to fetch applications list to validate master key
|
|
const response = await apiClient.listApplicationsAdmin({
|
|
limit: 1,
|
|
offset: 0,
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
set({ isLoading: false, isAdmin: true });
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: "Invalid master key",
|
|
isLoading: false,
|
|
isAdmin: false,
|
|
masterKey: null,
|
|
});
|
|
apiClient.clearMasterKey();
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error: "Invalid master key",
|
|
isLoading: false,
|
|
isAdmin: false,
|
|
masterKey: null,
|
|
});
|
|
apiClient.clearMasterKey();
|
|
return false;
|
|
}
|
|
},
|
|
|
|
clearCredentials: () => {
|
|
set({
|
|
paId: null,
|
|
paKey: null,
|
|
masterKey: null,
|
|
isAdmin: false,
|
|
currentApplication: null,
|
|
applicationList: [],
|
|
});
|
|
apiClient.clearMasterKey();
|
|
},
|
|
|
|
// Application CRUD
|
|
createApplication: async (
|
|
pdf?: File,
|
|
formData?: Partial<FormData>,
|
|
variant?: Variant,
|
|
) => {
|
|
set({ isSubmitting: true, error: null });
|
|
|
|
try {
|
|
const request = {
|
|
pdf,
|
|
variant,
|
|
return_format: "json" as const,
|
|
form_json_b64: formData
|
|
? createFormJsonFromData(formData)
|
|
: undefined,
|
|
};
|
|
|
|
const response = await apiClient.createApplication(request);
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const data = response.data as CreateResponse;
|
|
|
|
// Validate response data
|
|
if (!data.pa_id || !data.pa_key) {
|
|
set({
|
|
error:
|
|
"Unvollständige Antwort vom Server - ID oder Schlüssel fehlt",
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
set({
|
|
paId: data.pa_id,
|
|
paKey: data.pa_key,
|
|
successMessage: `Antrag erfolgreich erstellt! ID: ${data.pa_id}`,
|
|
isSubmitting: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
updateApplication: async (
|
|
pdf?: File,
|
|
formData?: Partial<FormData>,
|
|
variant?: Variant,
|
|
) => {
|
|
const { paId, paKey } = get();
|
|
if (!paId || !paKey) {
|
|
set({ error: "Keine Anmeldedaten verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
set({ isSubmitting: true, error: null });
|
|
|
|
try {
|
|
const request = {
|
|
pdf,
|
|
variant,
|
|
return_format: "json" as const,
|
|
form_json_b64: formData
|
|
? createFormJsonFromData(formData)
|
|
: undefined,
|
|
};
|
|
|
|
const response = await apiClient.updateApplication(
|
|
paId,
|
|
paKey,
|
|
request,
|
|
);
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const data = response.data as UpdateResponse;
|
|
set({
|
|
successMessage: `Antrag erfolgreich aktualisiert: ${data.pa_id}`,
|
|
isSubmitting: false,
|
|
isDirty: false,
|
|
});
|
|
|
|
// Reload current application
|
|
await get().loadApplication();
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
loadApplication: async (paId?: string, paKey?: string) => {
|
|
const state = get();
|
|
const id = paId || state.paId;
|
|
const key = paKey || state.paKey;
|
|
|
|
if (!id || !key) {
|
|
set({ error: "Keine Anmeldedaten verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.getApplication(id, key, "json");
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const application = response.data as ApplicationDetails;
|
|
|
|
// Convert application payload to form data
|
|
const convertedFormData = convertPayloadToFormData(
|
|
application.payload,
|
|
);
|
|
|
|
set({
|
|
currentApplication: application,
|
|
formData: { ...initialFormData, ...convertedFormData },
|
|
isLoading: false,
|
|
isDirty: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
deleteApplication: async (paId?: string, paKey?: string) => {
|
|
const state = get();
|
|
const id = paId || state.paId;
|
|
const key = paKey || state.paKey;
|
|
|
|
if (!id || !key) {
|
|
set({ error: "Keine Anmeldedaten verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.deleteApplication(id, key);
|
|
|
|
if (isSuccessResponse(response)) {
|
|
set({
|
|
currentApplication: null,
|
|
successMessage: "Antrag erfolgreich gelöscht",
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
downloadApplicationPdf: async (paId?: string, paKey?: string) => {
|
|
const state = get();
|
|
const id = paId || state.paId;
|
|
const key = paKey || state.paKey;
|
|
|
|
console.log("Starting PDF download for application:", id);
|
|
|
|
if (!id || !key) {
|
|
console.error("PDF download failed: Missing credentials", {
|
|
id,
|
|
key: key ? "***" : null,
|
|
});
|
|
set({ error: "Keine Anmeldedaten verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
console.log("Making API call for PDF download...");
|
|
const response = await apiClient.getApplication(id, key, "pdf");
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const blob = response.data as Blob;
|
|
console.log("Received PDF blob:", {
|
|
size: blob?.size,
|
|
type: blob?.type,
|
|
});
|
|
|
|
// Check if blob is valid
|
|
if (!blob || blob.size === 0) {
|
|
set({
|
|
error:
|
|
"PDF konnte nicht generiert werden - leere Antwort vom Server",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// Check for browser support
|
|
if (!window.URL || !window.URL.createObjectURL) {
|
|
set({
|
|
error:
|
|
"Ihr Browser unterstützt das Herunterladen von Dateien nicht",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
console.log("Creating download link for PDF...");
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = `antrag-${id}.pdf`;
|
|
|
|
// Make link invisible and add to DOM
|
|
link.style.display = "none";
|
|
document.body.appendChild(link);
|
|
|
|
// Trigger download
|
|
console.log("Triggering PDF download...");
|
|
link.click();
|
|
|
|
// Cleanup
|
|
setTimeout(() => {
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
}, 100);
|
|
|
|
set({
|
|
successMessage: "PDF wird heruntergeladen...",
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} catch (downloadError) {
|
|
console.error("Download error:", downloadError);
|
|
set({
|
|
error:
|
|
"Fehler beim Herunterladen der PDF-Datei. Bitte versuchen Sie es erneut.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
console.error("PDF download error:", error);
|
|
let errorMessage = "Unbekannter Fehler beim Herunterladen";
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes("404")) {
|
|
errorMessage = "Antrag nicht gefunden";
|
|
} else if (error.message.includes("403")) {
|
|
errorMessage = "Keine Berechtigung zum Herunterladen";
|
|
} else if (error.message.includes("500")) {
|
|
errorMessage = "Server-Fehler beim Generieren der PDF";
|
|
} else {
|
|
errorMessage = `Download-Fehler: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
set({
|
|
error: errorMessage,
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
// Application list management
|
|
loadApplicationList: async (refresh = false) => {
|
|
const {
|
|
paId,
|
|
paKey,
|
|
isAdmin,
|
|
currentPage,
|
|
itemsPerPage,
|
|
statusFilter,
|
|
variantFilter,
|
|
} = get();
|
|
|
|
if (!isAdmin && (!paId || !paKey)) {
|
|
set({ error: "Keine Anmeldedaten verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
let response;
|
|
|
|
if (isAdmin) {
|
|
// Ensure master key is set before calling admin API
|
|
if (!get().masterKey) {
|
|
set({
|
|
error: "Master key not available. Please login again.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
response = await apiClient.listApplicationsAdmin({
|
|
limit: itemsPerPage,
|
|
offset: currentPage * itemsPerPage,
|
|
status: statusFilter || undefined,
|
|
variant: variantFilter || undefined,
|
|
order_by: get().sortBy,
|
|
order: get().sortOrder,
|
|
});
|
|
} else {
|
|
response = await apiClient.listApplications(paId!, paKey!, {
|
|
limit: itemsPerPage,
|
|
offset: currentPage * itemsPerPage,
|
|
status: statusFilter || undefined,
|
|
variant: variantFilter || undefined,
|
|
order_by: get().sortBy,
|
|
order: get().sortOrder,
|
|
});
|
|
}
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const applications = response.data;
|
|
set({
|
|
applicationList: refresh
|
|
? applications
|
|
: [...get().applicationList, ...applications],
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
searchApplications: async (query: string) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Suche nur für Administratoren verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
// Ensure master key is set before calling admin API
|
|
if (!get().masterKey) {
|
|
set({
|
|
error: "Master key not available. Please login again.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null, searchQuery: query });
|
|
|
|
try {
|
|
const response = await apiClient.searchApplications({
|
|
q: query,
|
|
limit: get().itemsPerPage,
|
|
offset: 0,
|
|
status: get().statusFilter || undefined,
|
|
variant: get().variantFilter || undefined,
|
|
order_by: get().sortBy,
|
|
order: get().sortOrder,
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
set({
|
|
applicationList: response.data,
|
|
currentPage: 0,
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
searchApplicationsAdvanced: async (params: any) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Suche nur für Administratoren verfügbar" });
|
|
return false;
|
|
}
|
|
|
|
if (!get().masterKey) {
|
|
set({
|
|
error: "Master key not available. Please login again.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.searchApplications({
|
|
q: params.q,
|
|
status: params.status,
|
|
variant: params.variant,
|
|
amount_min: params.amount_min,
|
|
amount_max: params.amount_max,
|
|
date_from: params.date_from,
|
|
date_to: params.date_to,
|
|
created_by: params.created_by,
|
|
has_attachments: params.has_attachments,
|
|
limit: params.limit || get().itemsPerPage,
|
|
offset: params.offset || 0,
|
|
order_by: get().sortBy,
|
|
order: get().sortOrder,
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
set({
|
|
applicationList: response.data,
|
|
currentPage: 0,
|
|
isLoading: false,
|
|
searchQuery: params.q || null,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error: any) {
|
|
set({
|
|
error: error.message || "Suche fehlgeschlagen",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
setStatusFilter: (status) => {
|
|
set({ statusFilter: status, currentPage: 0 });
|
|
},
|
|
|
|
setVariantFilter: (variant) => {
|
|
set({ variantFilter: variant, currentPage: 0 });
|
|
},
|
|
|
|
setPage: (page) => {
|
|
set({ currentPage: page });
|
|
},
|
|
|
|
setSorting: (sortBy, sortOrder) => {
|
|
set({ sortBy, sortOrder, currentPage: 0 });
|
|
},
|
|
|
|
// Admin operations
|
|
loadApplicationAdmin: async (paId: string) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Admin-Berechtigung erforderlich" });
|
|
return false;
|
|
}
|
|
|
|
// Ensure master key is set before calling admin API
|
|
if (!get().masterKey) {
|
|
set({
|
|
error: "Master key not available. Please login again.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.getApplicationAdmin(paId, "json");
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const application = response.data as ApplicationDetails;
|
|
const convertedFormData = convertPayloadToFormData(
|
|
application.payload,
|
|
);
|
|
|
|
set({
|
|
currentApplication: application,
|
|
formData: { ...initialFormData, ...convertedFormData },
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
loadApplicationListAdmin: async () => {
|
|
return get().loadApplicationList(true);
|
|
},
|
|
|
|
updateApplicationStatusAdmin: async (paId: string, status: string) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Admin-Berechtigung erforderlich" });
|
|
return false;
|
|
}
|
|
|
|
// Ensure master key is set before calling admin API
|
|
if (!get().masterKey) {
|
|
set({
|
|
error: "Master key not available. Please login again.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.setApplicationStatusAdmin(
|
|
paId,
|
|
status,
|
|
);
|
|
|
|
if (isSuccessResponse(response)) {
|
|
// Update the application in the list
|
|
const updatedList = get().applicationList.map((app) =>
|
|
app.pa_id === paId ? { ...app, status } : app,
|
|
);
|
|
|
|
// Also update current application if it matches
|
|
const currentApp = get().currentApplication;
|
|
if (currentApp && currentApp.pa_id === paId) {
|
|
set({
|
|
currentApplication: { ...currentApp, status },
|
|
applicationList: updatedList,
|
|
successMessage: `Status erfolgreich auf "${status}" geändert`,
|
|
isLoading: false,
|
|
});
|
|
} else {
|
|
set({
|
|
applicationList: updatedList,
|
|
successMessage: `Status erfolgreich auf "${status}" geändert`,
|
|
isLoading: false,
|
|
});
|
|
}
|
|
return true;
|
|
} else {
|
|
const errorMsg = getErrorMessage(response);
|
|
console.error(`Status update failed for ${paId}:`, errorMsg);
|
|
set({
|
|
error: `Fehler beim Ändern des Status: ${errorMsg}`,
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
console.error(`Status update error for ${paId}:`, error);
|
|
let errorMessage = "Unbekannter Fehler beim Ändern des Status";
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes("500")) {
|
|
errorMessage =
|
|
"Server-Fehler beim Ändern des Status. Bitte versuchen Sie es später erneut.";
|
|
} else if (error.message.includes("403")) {
|
|
errorMessage = "Keine Berechtigung zum Ändern des Status";
|
|
} else if (error.message.includes("404")) {
|
|
errorMessage = "Antrag nicht gefunden";
|
|
} else {
|
|
errorMessage = `Fehler beim Ändern des Status: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
set({
|
|
error: errorMessage,
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
downloadApplicationPdfAdmin: async (paId: string) => {
|
|
console.log("Starting admin PDF download for application:", paId);
|
|
|
|
if (!get().isAdmin) {
|
|
console.error("Admin PDF download failed: Not an admin");
|
|
set({ error: "Admin-Berechtigung erforderlich" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
console.log("Making admin API call for PDF download...");
|
|
const response = await apiClient.getApplicationAdmin(paId, "pdf");
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const blob = response.data as Blob;
|
|
console.log("Received admin PDF blob:", {
|
|
size: blob?.size,
|
|
type: blob?.type,
|
|
});
|
|
|
|
// Check if blob is valid
|
|
if (!blob || blob.size === 0) {
|
|
set({
|
|
error:
|
|
"PDF konnte nicht generiert werden - leere Antwort vom Server",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// Check for browser support
|
|
if (!window.URL || !window.URL.createObjectURL) {
|
|
set({
|
|
error:
|
|
"Ihr Browser unterstützt das Herunterladen von Dateien nicht",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
console.log("Creating admin download link for PDF...");
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = `antrag-${paId}.pdf`;
|
|
|
|
// Make link invisible and add to DOM
|
|
link.style.display = "none";
|
|
document.body.appendChild(link);
|
|
|
|
// Trigger download
|
|
console.log("Triggering admin PDF download...");
|
|
link.click();
|
|
|
|
// Cleanup
|
|
setTimeout(() => {
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
}, 100);
|
|
|
|
set({
|
|
successMessage: "PDF wird heruntergeladen...",
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} catch (downloadError) {
|
|
console.error("Admin download error:", downloadError);
|
|
set({
|
|
error:
|
|
"Fehler beim Herunterladen der PDF-Datei. Bitte versuchen Sie es erneut.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
console.error("Admin PDF download error:", error);
|
|
let errorMessage = "Unbekannter Fehler beim Herunterladen";
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes("404")) {
|
|
errorMessage = "Antrag nicht gefunden";
|
|
} else if (error.message.includes("403")) {
|
|
errorMessage = "Keine Berechtigung zum Herunterladen";
|
|
} else if (error.message.includes("500")) {
|
|
errorMessage = "Server-Fehler beim Generieren der PDF";
|
|
} else {
|
|
errorMessage = `Download-Fehler: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
set({
|
|
error: errorMessage,
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
deleteApplicationAdmin: async (paId: string) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Admin-Berechtigung erforderlich" });
|
|
return false;
|
|
}
|
|
|
|
// Ensure master key is set before calling admin API
|
|
if (!get().masterKey) {
|
|
set({
|
|
error: "Master key not available. Please login again.",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.deleteApplicationAdmin(paId);
|
|
|
|
if (isSuccessResponse(response)) {
|
|
// Remove from the list
|
|
const updatedList = get().applicationList.filter(
|
|
(app) => app.pa_id !== paId,
|
|
);
|
|
|
|
set({
|
|
applicationList: updatedList,
|
|
successMessage: "Antrag erfolgreich gelöscht",
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
updateApplicationAdmin: async (
|
|
paId: string,
|
|
pdf?: File,
|
|
formData?: Partial<FormData>,
|
|
variant?: Variant,
|
|
) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Admin-Berechtigung erforderlich" });
|
|
return false;
|
|
}
|
|
|
|
set({ isSubmitting: true, error: null });
|
|
|
|
try {
|
|
const request = {
|
|
pdf,
|
|
variant,
|
|
return_format: "json" as const,
|
|
form_json_b64: formData
|
|
? createFormJsonFromData(formData)
|
|
: undefined,
|
|
};
|
|
|
|
const response = await apiClient.updateApplicationAdmin(
|
|
paId,
|
|
request,
|
|
);
|
|
|
|
if (isSuccessResponse(response)) {
|
|
const data = response.data as UpdateResponse;
|
|
set({
|
|
successMessage: `Antrag erfolgreich aktualisiert: ${data.pa_id}`,
|
|
isSubmitting: false,
|
|
isDirty: false,
|
|
});
|
|
|
|
// Reload current application
|
|
await get().loadApplicationAdmin(paId);
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Unbekannter Fehler",
|
|
isSubmitting: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
resetApplicationCredentials: async (paId: string) => {
|
|
if (!get().isAdmin) {
|
|
set({ error: "Admin-Berechtigung erforderlich" });
|
|
return null;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.resetApplicationCredentials(paId);
|
|
|
|
if (!response.success) {
|
|
throw new Error(response.error.detail);
|
|
}
|
|
|
|
set({
|
|
successMessage: `Neue Zugangsdaten generiert für Antrag ${paId}`,
|
|
isLoading: false,
|
|
});
|
|
|
|
return {
|
|
pa_id: response.data.pa_id,
|
|
pa_key: response.data.pa_key,
|
|
};
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Fehler beim Zurücksetzen der Zugangsdaten",
|
|
isLoading: false,
|
|
});
|
|
return null;
|
|
}
|
|
},
|
|
|
|
// Form management
|
|
updateFormData: (data) => {
|
|
set((state) => ({
|
|
formData: { ...state.formData, ...data },
|
|
isDirty: true,
|
|
}));
|
|
},
|
|
|
|
resetFormData: () => {
|
|
set({ formData: initialFormData, isDirty: false });
|
|
},
|
|
|
|
setFormData: (data) => {
|
|
set({ formData: { ...initialFormData, ...data }, isDirty: false });
|
|
},
|
|
|
|
markFormDirty: () => {
|
|
set({ isDirty: true });
|
|
},
|
|
|
|
markFormClean: () => {
|
|
set({ isDirty: false });
|
|
},
|
|
|
|
// UI state management
|
|
setLoading: (loading) => set({ isLoading: loading }),
|
|
setSubmitting: (submitting) => set({ isSubmitting: submitting }),
|
|
setError: (error) => set({ error }),
|
|
setSuccessMessage: (message) => set({ successMessage: message }),
|
|
|
|
clearMessages: () => {
|
|
set({ error: null, successMessage: null });
|
|
},
|
|
|
|
// Utility
|
|
reset: () => {
|
|
set(initialState);
|
|
apiClient.clearMasterKey();
|
|
},
|
|
|
|
isCurrentUserApplication: (paId: string) => {
|
|
const { paId: currentPaId, isAdmin } = get();
|
|
return isAdmin || currentPaId === paId;
|
|
},
|
|
|
|
// Bulk operations
|
|
bulkDeleteApplications: async (paIds: string[]) => {
|
|
if (!get().isAdmin || !get().masterKey) {
|
|
set({ error: "Admin access required" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.bulkOperation({
|
|
pa_ids: paIds,
|
|
operation: "delete",
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
// Reload application list to reflect changes
|
|
await get().loadApplicationListAdmin();
|
|
set({
|
|
successMessage: `${response.data.success.length} Anträge erfolgreich gelöscht`,
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Fehler beim Löschen",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
bulkApproveApplications: async (paIds: string[]) => {
|
|
if (!get().isAdmin || !get().masterKey) {
|
|
set({ error: "Admin access required" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.bulkOperation({
|
|
pa_ids: paIds,
|
|
operation: "approve",
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
await get().loadApplicationListAdmin();
|
|
set({
|
|
successMessage: `${response.data.success.length} Anträge erfolgreich genehmigt`,
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Fehler beim Genehmigen",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
bulkRejectApplications: async (paIds: string[]) => {
|
|
if (!get().isAdmin || !get().masterKey) {
|
|
set({ error: "Admin access required" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.bulkOperation({
|
|
pa_ids: paIds,
|
|
operation: "reject",
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
await get().loadApplicationListAdmin();
|
|
set({
|
|
successMessage: `${response.data.success.length} Anträge erfolgreich abgelehnt`,
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error ? error.message : "Fehler beim Ablehnen",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
bulkSetInReviewApplications: async (paIds: string[]) => {
|
|
if (!get().isAdmin || !get().masterKey) {
|
|
set({ error: "Admin access required" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.bulkOperation({
|
|
pa_ids: paIds,
|
|
operation: "set_in_review",
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
await get().loadApplicationListAdmin();
|
|
set({
|
|
successMessage: `${response.data.success.length} Anträge für Bearbeitung gesperrt`,
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Fehler beim Status-Update",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
|
|
bulkSetNewApplications: async (paIds: string[]) => {
|
|
if (!get().isAdmin || !get().masterKey) {
|
|
set({ error: "Admin access required" });
|
|
return false;
|
|
}
|
|
|
|
set({ isLoading: true, error: null });
|
|
|
|
try {
|
|
const response = await apiClient.bulkOperation({
|
|
pa_ids: paIds,
|
|
operation: "set_new",
|
|
});
|
|
|
|
if (isSuccessResponse(response)) {
|
|
await get().loadApplicationListAdmin();
|
|
set({
|
|
successMessage: `${response.data.success.length} Anträge auf "Beantragt" gesetzt`,
|
|
isLoading: false,
|
|
});
|
|
return true;
|
|
} else {
|
|
set({
|
|
error: getErrorMessage(response),
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Fehler beim Status-Update",
|
|
isLoading: false,
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
}),
|
|
{
|
|
name: "stupa-application-storage",
|
|
storage: createJSONStorage(() => localStorage),
|
|
partialize: (state) => ({
|
|
paId: state.paId,
|
|
paKey: state.paKey,
|
|
masterKey: state.masterKey,
|
|
isAdmin: state.isAdmin,
|
|
}),
|
|
onRehydrateStorage: () => (state) => {
|
|
// Immediately set master key in API client after rehydration
|
|
if (state?.masterKey && state?.isAdmin) {
|
|
apiClient.setMasterKey(state.masterKey);
|
|
}
|
|
},
|
|
},
|
|
),
|
|
);
|
|
|
|
// Helper functions
|
|
function createFormJsonFromData(formData: Partial<FormData>): string {
|
|
const formJson: Record<string, { "/V": any }> = {};
|
|
|
|
// Map form data to PDF field names based on the mapping
|
|
const fieldMappings: Record<string, string> = {
|
|
// Meta
|
|
paId: "pa-id",
|
|
paKey: "pa-key",
|
|
|
|
// Applicant
|
|
applicantType: "pa-applicant-type",
|
|
institutionType: "pa-institution-type",
|
|
institutionName: "pa-institution",
|
|
firstName: "pa-first-name",
|
|
lastName: "pa-last-name",
|
|
email: "pa-email",
|
|
phone: "pa-phone",
|
|
course: "pa-course",
|
|
role: "pa-role",
|
|
|
|
// Project
|
|
projectName: "pa-project-name",
|
|
startDate: "pa-start-date",
|
|
endDate: "pa-end-date",
|
|
participants: "pa-participants",
|
|
description: "pa-project-description",
|
|
|
|
// Totals
|
|
requestedAmountEur: "pa-requested-amount-euro-sum",
|
|
};
|
|
|
|
// Add basic fields
|
|
for (const [formKey, pdfKey] of Object.entries(fieldMappings)) {
|
|
const value = (formData as any)[formKey];
|
|
if (value !== undefined && value !== null && value !== "") {
|
|
formJson[pdfKey] = { "/V": value };
|
|
}
|
|
}
|
|
|
|
// Add participating faculties
|
|
if (formData.participatingFaculties) {
|
|
const faculties = formData.participatingFaculties;
|
|
formJson["pa-participating-faculties-inf"] = { "/V": faculties.inf };
|
|
formJson["pa-participating-faculties-esb"] = { "/V": faculties.esb };
|
|
formJson["pa-participating-faculties-ls"] = { "/V": faculties.ls };
|
|
formJson["pa-participating-faculties-tec"] = { "/V": faculties.tec };
|
|
formJson["pa-participating-faculties-tex"] = { "/V": faculties.tex };
|
|
formJson["pa-participating-faculties-nxt"] = { "/V": faculties.nxt };
|
|
formJson["pa-participating-faculties-open"] = { "/V": faculties.open };
|
|
}
|
|
|
|
// Add costs
|
|
if (formData.costs) {
|
|
formData.costs.forEach((cost, index) => {
|
|
const i = index + 1; // 1-based indexing
|
|
if (cost.name) {
|
|
formJson[`pa-cost-${i}-name`] = { "/V": cost.name };
|
|
}
|
|
if (cost.amountEur !== undefined) {
|
|
formJson[`pa-cost-${i}-amount-euro`] = { "/V": cost.amountEur };
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add variant-specific fields
|
|
if (formData.variant === "QSM") {
|
|
if (formData.financingCode || formData.qsmFinancing) {
|
|
formJson["pa-qsm-financing"] = {
|
|
"/V": formData.financingCode || formData.qsmFinancing,
|
|
};
|
|
}
|
|
if (formData.qsmFlags) {
|
|
formJson["pa-qsm-stellenfinanzierungen"] = {
|
|
"/V": formData.qsmFlags.stellenfinanzierungen,
|
|
};
|
|
formJson["pa-qsm-studierende"] = { "/V": formData.qsmFlags.studierende };
|
|
formJson["pa-qsm-individuell"] = { "/V": formData.qsmFlags.individuell };
|
|
formJson["pa-qsm-exkursion-genehmigt"] = {
|
|
"/V": formData.qsmFlags.exkursionGenehmigt,
|
|
};
|
|
formJson["pa-qsm-exkursion-bezuschusst"] = {
|
|
"/V": formData.qsmFlags.exkursionBezuschusst,
|
|
};
|
|
}
|
|
} else if (formData.variant === "VSM") {
|
|
if (formData.financingCode || formData.vsmFinancing) {
|
|
formJson["pa-vsm-financing"] = {
|
|
"/V": formData.financingCode || formData.vsmFinancing,
|
|
};
|
|
}
|
|
if (formData.vsmFlags) {
|
|
formJson["pa-vsm-aufgaben"] = { "/V": formData.vsmFlags.aufgaben };
|
|
formJson["pa-vsm-individuell"] = { "/V": formData.vsmFlags.individuell };
|
|
}
|
|
}
|
|
|
|
const jsonString = JSON.stringify(formJson);
|
|
return btoa(unescape(encodeURIComponent(jsonString))); // Base64 encode with UTF-8 support
|
|
}
|
|
|
|
function convertPayloadToFormData(payload: RootPayload): Partial<FormData> {
|
|
const pa = payload.pa;
|
|
|
|
return {
|
|
paId: pa.meta.id,
|
|
paKey: pa.meta.key,
|
|
applicantType: pa.applicant.type as any,
|
|
institutionType: pa.applicant.institution.type as any,
|
|
institutionName: pa.applicant.institution.name || "",
|
|
firstName: pa.applicant.name.first || "",
|
|
lastName: pa.applicant.name.last || "",
|
|
email: pa.applicant.contact.email || "",
|
|
phone: pa.applicant.contact.phone || "",
|
|
course: pa.applicant.course as any,
|
|
role: pa.applicant.role as any,
|
|
projectName: pa.project.name || "",
|
|
startDate: pa.project.dates.start || "",
|
|
endDate: pa.project.dates.end || "",
|
|
participants: pa.project.participants,
|
|
description: pa.project.description || "",
|
|
participatingFaculties: {
|
|
inf: pa.project.participation.faculties.inf || false,
|
|
esb: pa.project.participation.faculties.esb || false,
|
|
ls: pa.project.participation.faculties.ls || false,
|
|
tec: pa.project.participation.faculties.tec || false,
|
|
tex: pa.project.participation.faculties.tex || false,
|
|
nxt: pa.project.participation.faculties.nxt || false,
|
|
open: pa.project.participation.faculties.open || false,
|
|
},
|
|
costs: (pa.project.costs || []).map((cost) => ({
|
|
name: cost.name || "",
|
|
amountEur: cost.amountEur || 0,
|
|
})),
|
|
variant: (pa.project.financing.vsm?.code
|
|
? "VSM"
|
|
: pa.project.financing.qsm?.code
|
|
? "QSM"
|
|
: "COMMON") as Variant,
|
|
financingCode:
|
|
pa.project.financing.qsm?.code || pa.project.financing.vsm?.code || "",
|
|
qsmFinancing: pa.project.financing.qsm?.code as any,
|
|
qsmFlags: pa.project.financing.qsm?.flags
|
|
? {
|
|
stellenfinanzierungen:
|
|
pa.project.financing.qsm.flags.stellenfinanzierungen || false,
|
|
studierende: pa.project.financing.qsm.flags.studierende || false,
|
|
individuell: pa.project.financing.qsm.flags.individuell || false,
|
|
exkursionGenehmigt:
|
|
pa.project.financing.qsm.flags.exkursionGenehmigt || false,
|
|
exkursionBezuschusst:
|
|
pa.project.financing.qsm.flags.exkursionBezuschusst || false,
|
|
}
|
|
: undefined,
|
|
vsmFinancing: pa.project.financing.vsm?.code as any,
|
|
vsmFlags: pa.project.financing.vsm?.flags
|
|
? {
|
|
aufgaben: pa.project.financing.vsm.flags.aufgaben || false,
|
|
individuell: pa.project.financing.vsm.flags.individuell || false,
|
|
}
|
|
: undefined,
|
|
};
|
|
}
|