import { db, deleteEntry, getDay, getEntries } from './db.js'; import { localDateKey } from './time.js'; const conflict = () => new Error('Ce poste a changé. Fermez puis ouvrez à nouveau la correction.'); const fields = ['id', 'date', 'seq', 'kind', 'task', 'client', 'startTs', 'endTs']; async function readOriginal(original) { const entry = await db.entries.get(original.id); if (!entry || fields.some((key) => entry[key] !== original[key])) throw conflict(); const day = await getDay(entry.date); if (!day) throw conflict(); return { entry, day, entries: await getEntries(entry.date) }; } function content(values) { if (!['work', 'travel', 'lunch'].includes(values.kind)) throw new Error('Choisissez un type de poste.'); const task = values.kind === 'lunch' ? 'Pause déjeuner' : String(values.task ?? '').trim(); if (!task) throw new Error('Renseignez le nom de la tâche.'); return { kind: values.kind, task, client: values.kind === 'lunch' ? null : String(values.client ?? '').trim() || null }; } function validateTimes(start, end, now) { if (!Number.isFinite(start) || !Number.isFinite(end) || start >= end) { throw new Error('La fin doit être postérieure au début.'); } if (end > now) throw new Error('Les horaires ne peuvent pas être dans le futur.'); } async function saveLunch(date, values) { if (values.kind !== 'lunch') return; if (!['panier', 'restaurant'].includes(values.lunchType)) throw new Error('Choisissez le type de repas.'); await db.days.update(date, { lunchType: values.lunchType, restaurant: values.lunchType === 'restaurant' ? String(values.restaurant ?? '').trim() || null : null, }); } /** Correct both boundaries and their neighbours without changing day status. */ export function saveRecordedEntry(original, values, now = Date.now()) { return db.transaction('rw', db.days, db.entries, async () => { const { entry, day, entries } = await readOriginal(original); const index = entries.findIndex((e) => e.id === entry.id); const prev = entries[index - 1], next = entries[index + 1]; const { startTs, endTs } = values; if ((entry.endTs == null) !== (endTs == null)) throw conflict(); validateTimes(startTs, endTs ?? now, now); if (prev && startTs < prev.startTs || next && endTs > (next.endTs ?? now)) { throw new Error('Ces horaires dépassent le poste voisin. Corrigez d’abord ce voisin.'); } if (!prev && localDateKey(startTs) !== day.date) throw new Error('Le début doit rester à la date de cette journée.'); await db.entries.update(entry.id, { ...content(values), startTs, endTs }); if (prev && startTs !== entry.startTs) await db.entries.update(prev.id, { endTs: startTs }); if (next && endTs !== entry.endTs) await db.entries.update(next.id, { startTs: endTs }); if (!prev) await db.days.update(day.date, { startedTs: startTs }); if (!next && day.status === 'closed') await db.days.update(day.date, { endedTs: endTs }); await saveLunch(day.date, values); }); } /** Carve a forgotten interval out of a selected entry, preserving both remnants. */ export function insertRecordedEntry(original, values, now = Date.now()) { return db.transaction('rw', db.days, db.entries, async () => { const { entry, entries } = await readOriginal(original); const { startTs, endTs } = values; validateTimes(startTs, endTs, now); if (startTs < entry.startTs || endTs > (entry.endTs ?? now)) { throw new Error('Le poste ajouté doit rester dans la plage choisie.'); } const { id, ...rest } = entry; const pieces = []; if (startTs > entry.startTs) pieces.push({ ...rest, endTs: startTs }); const insertedIndex = pieces.length; pieces.push({ ...rest, ...content(values), startTs, endTs }); if (entry.endTs == null || endTs < entry.endTs) pieces.push({ ...rest, startTs: endTs }); // Keep the original id on a surviving portion, or on the replacement when full. const keepIndex = startTs > entry.startTs ? 0 : pieces.length - 1; const shift = pieces.length - 1; for (const later of entries.filter((e) => e.seq > entry.seq)) { await db.entries.update(later.id, { seq: later.seq + shift }); } let insertedId; for (let i = 0; i < pieces.length; i++) { const piece = { ...pieces[i], seq: entry.seq + i }; const pieceId = i === keepIndex ? (await db.entries.put({ ...piece, id }), id) : await db.entries.add(piece); if (i === insertedIndex) insertedId = pieceId; } await saveLunch(entry.date, values); return insertedId; }); } export function removeRecordedEntry(original) { return db.transaction('rw', db.days, db.entries, async () => { const { entry, entries } = await readOriginal(original); if (entries.length < 2) throw new Error('Le dernier poste de la journée ne peut pas être supprimé.'); if (entry.endTs == null) throw new Error('Le poste en cours ne peut pas être supprimé.'); await deleteEntry(entry.id); }); } /** Native datetime-local inputs use wall time, never UTC. */ export function localInput(ts) { const d = new Date(ts); return `${localDateKey(ts)}T${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }