fix(export): Stundennachweis nur fuer laufende Projekte auswaehlbar

Die Auswahl im Exportdialog kam aus allen zugewiesenen Projekten - also
auch aus archivierten und von stillgelegten Kunden. In der Liste stand
damit etwa "Top IT Services", wo seit 2025 nichts mehr laeuft.

Waehlbar sind jetzt nur laufende Projekte aktiver Kunden. Dafuer liefert
loadProjects den Status des Kunden mit; ohne ihn liesse sich "nur aktive
Kunden" in der Oberflaeche nicht entscheiden.

Die Zeiten aus archivierten Projekten bleiben erreichbar: ohne Filter
exportiert der Nachweis alles, was die Person im Monat gebucht hat, und
der Projektreport hat seinen eigenen Export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-21 18:08:05 +02:00
co-authored by Claude Opus 5
parent 0cd426777d
commit 4e46df813c
3 changed files with 14 additions and 5 deletions
+2 -1
View File
@@ -175,6 +175,7 @@ export function isBillable(project, task) {
export async function loadProjects(settings, { where = '1=1', params = [] } = {}) { export async function loadProjects(settings, { where = '1=1', params = [] } = {}) {
const [projects] = await pool.query( const [projects] = await pool.query(
`SELECT p.*, c.name AS customer_name, c.short_name AS customer_short, c.color AS customer_color, `SELECT p.*, c.name AS customer_name, c.short_name AS customer_short, c.color AS customer_color,
c.is_active AS customer_active,
u.name AS owner_name u.name AS owner_name
FROM pulse_projects p FROM pulse_projects p
JOIN pulse_customers c ON c.id = p.customer_id JOIN pulse_customers c ON c.id = p.customer_id
@@ -270,7 +271,7 @@ export async function loadProjects(settings, { where = '1=1', params = [] } = {}
return { return {
...project, ...project,
display_name: displayName(settings, project, customer), display_name: displayName(settings, project, customer),
customer: { id: p.customer_id, name: p.customer_name, short_name: p.customer_short, color: p.customer_color }, customer: { id: p.customer_id, name: p.customer_name, short_name: p.customer_short, color: p.customer_color, is_active: Boolean(p.customer_active) },
pots: projectPots, pots: projectPots,
tasks: tasks tasks: tasks
.filter((t) => t.project_id === p.id) .filter((t) => t.project_id === p.id)
+11 -3
View File
@@ -33,12 +33,20 @@ export const ExportDialog: React.FC<{
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const isLead = atLeast(user, 'lead'); const isLead = atLeast(user, 'lead');
/*
* Zur Auswahl stehen nur laufende Projekte aktiver Kunden. Archivierte
* Projekte und stillgelegte Kunden wuerden die Liste mit Aufträgen fuellen,
* fuer die niemand mehr einen Nachweis schreibt. Ihre Zeiten sind dadurch
* nicht verloren: ohne Filter exportiert der Nachweis alles, was die Person
* im Monat gebucht hat - und der Projektreport hat einen eigenen Export.
*/
const waehlbar = useMemo(() => projects.filter((p) => p.is_active && p.customer.is_active !== false), [projects]);
const customers = useMemo(() => { const customers = useMemo(() => {
const map = new Map<number, string>(); const map = new Map<number, string>();
projects.forEach((p) => map.set(p.customer.id, p.customer.name)); waehlbar.forEach((p) => map.set(p.customer.id, p.customer.name));
return [...map.entries()].sort((a, b) => a[1].localeCompare(b[1], 'de')); return [...map.entries()].sort((a, b) => a[1].localeCompare(b[1], 'de'));
}, [projects]); }, [waehlbar]);
const projectOptions = projects.filter((p) => !customerId || p.customer_id === customerId); const projectOptions = waehlbar.filter((p) => !customerId || p.customer_id === customerId);
// Monate: 18 zurueck, 2 voraus. // Monate: 18 zurueck, 2 voraus.
const months = useMemo(() => { const months = useMemo(() => {
+1 -1
View File
@@ -178,7 +178,7 @@ export interface Project {
owner_id: number | null; owner_id: number | null;
owner_name: string | null; owner_name: string | null;
is_active: boolean; is_active: boolean;
customer: { id: number; name: string; short_name: string; color: string | null }; customer: { id: number; name: string; short_name: string; color: string | null; is_active: boolean };
pots: BudgetPot[]; pots: BudgetPot[];
tasks: Task[]; tasks: Task[];
members: MemberRef[]; members: MemberRef[];