diff --git a/server/src/routes/imports.js b/server/src/routes/imports.js index 2262e9c..f0f0414 100644 --- a/server/src/routes/imports.js +++ b/server/src/routes/imports.js @@ -16,11 +16,21 @@ import { isBillable, resolvePot } from '../lib/projects.js'; * * Wiedererkennung ueber harvest_id: ein zweiter Lauf legt nichts doppelt an, * sondern meldet die Buchung als "schon vorhanden". + * + * Eine Ausnahme von den Fachregeln: die Grenze von 24 Stunden je Tag gilt hier + * nicht. In Harvest stehen Sammelbuchungen ("Nachbuchung DORA September und + * Oktober", 80 Stunden auf einem Tag). Wer sie ablehnt, verliert die Stunden + * und damit das Restbudget des Projekts; wer sie auf Tage verteilt, erfindet + * Leistungsdaten. Sie kommen deshalb so herein, wie sie gebucht wurden. Im + * Buchungsdialog bleibt die Tagesgrenze bestehen. */ const router = express.Router(); const MAX_BATCH = 1000; +// Sammelbuchungen duerfen mehr als einen Tag umfassen - hoechstens aber 31 Tage, +// damit Zahlendreher nicht unbemerkt durchgehen. +const MAX_MINUTES = 31 * 24 * 60; router.post('/entries', async (req, res, next) => { const body = req.body || {}; @@ -47,7 +57,9 @@ router.post('/entries', async (req, res, next) => { const note = row.note == null ? null : String(row.note).trim().slice(0, 1000) || null; if (!parseDate(date)) throw new Error('Datum fehlt oder ist unguelltig.'); - if (!Number.isInteger(minutes) || minutes < 1 || minutes > 24 * 60) throw new Error('Minuten muessen zwischen 1 und 1440 liegen.'); + if (!Number.isInteger(minutes) || minutes < 1 || minutes > MAX_MINUTES) { + throw new Error(`Minuten muessen zwischen 1 und ${MAX_MINUTES} liegen.`); + } // Schon importiert? Dann nicht erneut anlegen. if (harvestId) { @@ -77,12 +89,6 @@ router.post('/entries', async (req, res, next) => { task = found; } - const [[day]] = await conn.query( - 'SELECT COALESCE(SUM(minutes), 0) AS total FROM pulse_time_entries WHERE user_id = ? AND entry_date = ?', - [userId, date] - ); - if (Number(day.total) + minutes > 24 * 60) throw new Error('An diesem Tag waeren es mehr als 24 Stunden.'); - const billable = isBillable(project, task); // Historische Uebernahme: abgelaufene Toepfe nehmen ihre eigenen Zeiten noch an. const pot = await resolvePot(conn, project, date, minutes, null, billable, { historic: true }); diff --git a/server/src/routes/projects.js b/server/src/routes/projects.js index c35da9d..14fe60d 100644 --- a/server/src/routes/projects.js +++ b/server/src/routes/projects.js @@ -195,7 +195,9 @@ router.post('/', requireRole('lead'), async (req, res, next) => { errors.push(...result.errors); pots.push(result.pot); } - } else if (!out.rate) { + } else if (out.type === 'abruf' && !out.rate) { + // Nur Abruf rechnet ueber den Satz am Projekt ab; nicht abrechenbare + // Projekte brauchen keinen - er wird gleich darunter ohnehin verworfen. errors.push('Abruf: Stundensatz oder Tagessatz fehlt.'); } if (out.type !== 'abruf') out.rate = null;