fix(historie): Uebernahme alter Zeiten, Rollenzone Externe, Reportfehler
Die Uebernahme der Harvest-Historie ist an drei Stellen haengen geblieben: - resolvePot hat abgelaufene Toepfe grundsaetzlich gesperrt. Fuer eine historische Buchung ist das falsch - dort zaehlt, ob der Topf am Leistungstag lief. Der Importweg setzt jetzt `historic`, das Volumen wird weiter geprueft, damit Ueberbuchungen auffallen. - In der SSO-Verwaltung fehlte die Ablagezone fuer die Rolle "Externe"; Konten liessen sich dort nicht zuordnen. - In buildProjectDetail und der Kundenauswertung stand ein Rest der Auslastungsaenderung (bisHeute/capTo) ohne Bezug im Scope. Beide Reportseiten liefen dadurch auf 500. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
79bfd561bd
commit
e962be41ea
@@ -343,8 +343,12 @@ export class BookingError extends Error {
|
|||||||
* - ist das Volumen erreicht oder das Ablaufdatum ueberschritten: nicht buchbar
|
* - ist das Volumen erreicht oder das Ablaufdatum ueberschritten: nicht buchbar
|
||||||
* - ueberlappen mehrere Toepfe, zuerst der, der am fruehesten endet
|
* - ueberlappen mehrere Toepfe, zuerst der, der am fruehesten endet
|
||||||
* - Projekte ohne Topf (z. B. intern, unbegrenzt) sind frei buchbar
|
* - Projekte ohne Topf (z. B. intern, unbegrenzt) sind frei buchbar
|
||||||
|
*
|
||||||
|
* Mit `historic` entfaellt die Ablaufsperre: bei der Uebernahme alter Zeiten
|
||||||
|
* zaehlt, ob der Topf am Leistungstag lief - nicht, ob er heute noch offen ist.
|
||||||
|
* Das Volumen wird weiter geprueft, damit Ueberbuchungen auffallen.
|
||||||
*/
|
*/
|
||||||
export async function resolvePot(conn, project, date, minutes, excludeEntryId = null, billable = true) {
|
export async function resolvePot(conn, project, date, minutes, excludeEntryId = null, billable = true, { historic = false } = {}) {
|
||||||
const onlyBillable = countsAgainstBudget(project) === 'billable';
|
const onlyBillable = countsAgainstBudget(project) === 'billable';
|
||||||
// Nicht abrechenbare Stunden in einem Billable-Projekt verbrauchen kein Volumen.
|
// Nicht abrechenbare Stunden in einem Billable-Projekt verbrauchen kein Volumen.
|
||||||
const needed = onlyBillable && !billable ? 0 : minutes;
|
const needed = onlyBillable && !billable ? 0 : minutes;
|
||||||
@@ -370,7 +374,7 @@ export async function resolvePot(conn, project, date, minutes, excludeEntryId =
|
|||||||
}
|
}
|
||||||
let lastShortage = null;
|
let lastShortage = null;
|
||||||
for (const pot of covering) {
|
for (const pot of covering) {
|
||||||
if (pot.valid_to < today()) {
|
if (!historic && pot.valid_to < today()) {
|
||||||
lastShortage = `Budgettopf „${pot.label}“ ist seit ${de(pot.valid_to)} abgelaufen.`;
|
lastShortage = `Budgettopf „${pot.label}“ ist seit ${de(pot.valid_to)} abgelaufen.`;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ router.post('/entries', async (req, res, next) => {
|
|||||||
if (Number(day.total) + minutes > 24 * 60) throw new Error('An diesem Tag waeren es mehr als 24 Stunden.');
|
if (Number(day.total) + minutes > 24 * 60) throw new Error('An diesem Tag waeren es mehr als 24 Stunden.');
|
||||||
|
|
||||||
const billable = isBillable(project, task);
|
const billable = isBillable(project, task);
|
||||||
const pot = await resolvePot(conn, project, date, minutes, null, billable);
|
// Historische Uebernahme: abgelaufene Toepfe nehmen ihre eigenen Zeiten noch an.
|
||||||
|
const pot = await resolvePot(conn, project, date, minutes, null, billable, { historic: true });
|
||||||
if (dryRun) return 0;
|
if (dryRun) return 0;
|
||||||
const [result] = await conn.query(
|
const [result] = await conn.query(
|
||||||
`INSERT INTO pulse_time_entries (user_id, project_id, task_id, pot_id, entry_date, minutes, note, billable, harvest_id)
|
`INSERT INTO pulse_time_entries (user_id, project_id, task_id, pot_id, entry_date, minutes, note, billable, harvest_id)
|
||||||
|
|||||||
@@ -167,15 +167,6 @@ function buildProjectDetail(project, entries) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
* Auslastung nur bis heute. Die Kapazitaet endet ohnehin am heutigen Tag
|
|
||||||
* (capTo) - werden Zeiten im Voraus gebucht, etwa eine ganze Woche Urlaub am
|
|
||||||
* Montag, stehen sonst 40 gebuchte Stunden gegen 8 Stunden Kapazitaet und die
|
|
||||||
* Auslastung springt auf 500 Prozent. Die Spalte "Stunden" zeigt weiter
|
|
||||||
* alles, was im Zeitraum gebucht ist.
|
|
||||||
*/
|
|
||||||
const bisHeute = entries.filter((e) => e.entry_date <= capTo);
|
|
||||||
|
|
||||||
const minutes = entries.reduce((s, e) => s + e.minutes, 0);
|
const minutes = entries.reduce((s, e) => s + e.minutes, 0);
|
||||||
const billable = entries.filter((e) => e.billable).reduce((s, e) => s + e.minutes, 0);
|
const billable = entries.filter((e) => e.billable).reduce((s, e) => s + e.minutes, 0);
|
||||||
return {
|
return {
|
||||||
@@ -228,14 +219,6 @@ router.get('/customer/:id', async (req, res, next) => {
|
|||||||
cursor.setUTCMonth(cursor.getUTCMonth() + 1);
|
cursor.setUTCMonth(cursor.getUTCMonth() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Auslastung nur bis heute. Die Kapazitaet endet ohnehin am heutigen Tag
|
|
||||||
* (capTo) - werden Zeiten im Voraus gebucht, etwa eine ganze Woche Urlaub am
|
|
||||||
* Montag, stehen sonst 40 gebuchte Stunden gegen 8 Stunden Kapazitaet und die
|
|
||||||
* Auslastung springt auf 500 Prozent. Die Spalte "Stunden" zeigt weiter
|
|
||||||
* alles, was im Zeitraum gebucht ist.
|
|
||||||
*/
|
|
||||||
const bisHeute = entries.filter((e) => e.entry_date <= capTo);
|
|
||||||
|
|
||||||
const minutes = entries.reduce((s, e) => s + e.minutes, 0);
|
const minutes = entries.reduce((s, e) => s + e.minutes, 0);
|
||||||
const billable = entries.filter((e) => e.billable).reduce((s, e) => s + e.minutes, 0);
|
const billable = entries.filter((e) => e.billable).reduce((s, e) => s + e.minutes, 0);
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type ImportRole = Exclude<Role, 'admin'>;
|
|||||||
|
|
||||||
const ZONES: { role: ImportRole; hint: string; icon: IconName; tone: string }[] = [
|
const ZONES: { role: ImportRole; hint: string; icon: IconName; tone: string }[] = [
|
||||||
{ role: 'user', hint: 'Bucht Zeiten auf zugewiesene Projekte', icon: 'user', tone: 'border-ink-300 bg-ink-50 text-ink-700' },
|
{ role: 'user', hint: 'Bucht Zeiten auf zugewiesene Projekte', icon: 'user', tone: 'border-ink-300 bg-ink-50 text-ink-700' },
|
||||||
|
// Gleiche Rechte wie Mitarbeiter; die Rolle trennt nur, wer zum Haus gehoert.
|
||||||
|
{ role: 'external', hint: 'Wie Mitarbeiter, gilt als externe Kraft', icon: 'globe', tone: 'border-amber-200 bg-amber-50 text-amber-700' },
|
||||||
{ role: 'lead', hint: 'Sieht und verwaltet sein Team', icon: 'team', tone: 'border-sky-200 bg-sky-50 text-sky-700' },
|
{ role: 'lead', hint: 'Sieht und verwaltet sein Team', icon: 'team', tone: 'border-sky-200 bg-sky-50 text-sky-700' },
|
||||||
{ role: 'management', hint: 'Sieht alles, verwaltet SSO', icon: 'shield', tone: 'border-violet-200 bg-violet-50 text-violet-700' },
|
{ role: 'management', hint: 'Sieht alles, verwaltet SSO', icon: 'shield', tone: 'border-violet-200 bg-violet-50 text-violet-700' },
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user