Added automation
This commit is contained in:
@@ -8,8 +8,6 @@
|
||||
|
||||
import {
|
||||
cleanupOldDays,
|
||||
closeAndStart,
|
||||
closeDay,
|
||||
createDay,
|
||||
deleteEntry,
|
||||
getActiveDay,
|
||||
@@ -18,10 +16,12 @@ import {
|
||||
importBackup,
|
||||
patchActiveDay,
|
||||
patchEntry,
|
||||
readSettings,
|
||||
reopenDay,
|
||||
setEntryStart,
|
||||
} from './db.js';
|
||||
import { buildModel, suggestClients, suggestTasks, TRANSPORT_TASK } from './predict.js';
|
||||
import { automateDay, manualAdvance, previousContext } from './automation.js';
|
||||
import { loadSettings, settings } from './settings.svelte.js';
|
||||
import { atLocalTime, HOUR, localDateKey, roundTs, weekday } from './time.js';
|
||||
|
||||
@@ -36,6 +36,7 @@ export const dayState = $state({
|
||||
closedDays: [],
|
||||
historyEntries: [],
|
||||
todayRecord: null,
|
||||
previousWork: null,
|
||||
/** Prediction model, rebuilt whenever history changes. */
|
||||
model: null,
|
||||
/** True once the configured end time has passed and the worker has not answered. */
|
||||
@@ -70,12 +71,15 @@ export function reconcile(now = Date.now()) {
|
||||
}
|
||||
|
||||
async function reconcileDay(now) {
|
||||
// A settings change in another tab must also stop this tab's clock actions.
|
||||
const persisted = await readSettings();
|
||||
if (persisted) Object.assign(settings, persisted);
|
||||
dayState.today = localDateKey(now);
|
||||
|
||||
const active = await getActiveDay();
|
||||
// Settle stale work before retention, so an open day cannot vanish on return.
|
||||
if (active && active.date < dayState.today) {
|
||||
await closeDay(active.date, now, true);
|
||||
await automateDay(active.date, settings, now, { stale: true });
|
||||
return reconcileDay(now);
|
||||
}
|
||||
|
||||
@@ -94,15 +98,18 @@ async function reconcileDay(now) {
|
||||
// already ended must not spring back to life.
|
||||
if (!active) {
|
||||
const existing = await getDay(dayState.today);
|
||||
if (!existing && settings.workdays.includes(weekday(dayState.today))) {
|
||||
if (!existing && settings.autoStartDay && settings.workdays.includes(weekday(dayState.today))) {
|
||||
const plannedStart = atLocalTime(dayState.today, settings.dayStart);
|
||||
if (now >= plannedStart) {
|
||||
await openDay(dayState.today, plannedStart);
|
||||
await automateDay(dayState.today, settings, now);
|
||||
return refresh(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active) await automateDay(active.date, settings, now);
|
||||
|
||||
await refresh(now);
|
||||
}
|
||||
|
||||
@@ -110,14 +117,16 @@ async function reconcileDay(now) {
|
||||
* Create a day and immediately open the transport task: per the workflow, the drive
|
||||
* out from base *is* the first task, and "Arrivé sur site" is what ends it.
|
||||
*/
|
||||
async function openDay(dateKey, startTs) {
|
||||
async function openDay(dateKey, startTs, manual = false) {
|
||||
const { entries, days } = await getHistory();
|
||||
const model = buildModel(entries, days);
|
||||
await createDay({
|
||||
date: dateKey,
|
||||
status: 'active',
|
||||
plannedStartTs: atLocalTime(dateKey, settings.dayStart),
|
||||
plannedEndTs: atLocalTime(dateKey, settings.dayEnd),
|
||||
plannedEndTs: manual && settings.autoEndDay
|
||||
? Math.max(atLocalTime(dateKey, settings.dayEnd), startTs + HOUR)
|
||||
: atLocalTime(dateKey, settings.dayEnd),
|
||||
startedTs: startTs,
|
||||
endedTs: null,
|
||||
lunchType: settings.lunchType,
|
||||
@@ -127,6 +136,7 @@ async function openDay(dateKey, startTs) {
|
||||
kind: 'travel',
|
||||
task: TRANSPORT_TASK,
|
||||
client: model.lastClient,
|
||||
automatic: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,6 +152,7 @@ export async function refresh(now = Date.now()) {
|
||||
const entriesFor = (day) => day ? entries.filter((e) => e.date === day.date).sort((a, b) => a.seq - b.seq) : [];
|
||||
dayState.today = localDateKey(now);
|
||||
dayState.todayRecord = days.find((d) => d.date === dayState.today) ?? null;
|
||||
dayState.previousWork = previousContext(days, entries, active?.date ?? dayState.today).previousWork;
|
||||
dayState.day = active ?? null;
|
||||
dayState.entries = entriesFor(active);
|
||||
dayState.lastClosed = lastClosed ?? null;
|
||||
@@ -161,7 +172,7 @@ export async function refresh(now = Date.now()) {
|
||||
/** Manual start, for a Saturday or an early departure. */
|
||||
export async function startDayNow(now = Date.now()) {
|
||||
if (dayState.importing) return;
|
||||
await openDay(localDateKey(now), now);
|
||||
await openDay(localDateKey(now), now, true);
|
||||
await refresh(now);
|
||||
}
|
||||
|
||||
@@ -174,17 +185,13 @@ export async function startDayNow(now = Date.now()) {
|
||||
*/
|
||||
export async function advance({ kind = 'work', task, client }, now = Date.now()) {
|
||||
if (!dayState.day || dayState.importing) return;
|
||||
await closeAndStart(dayState.day.date, now, { kind, task, client: client ?? null }, running()?.id ?? null);
|
||||
await manualAdvance(dayState.day.date, now, { kind, task, client: client ?? null }, running()?.id ?? null);
|
||||
await refresh(now);
|
||||
}
|
||||
|
||||
export async function startLunch(now = Date.now()) {
|
||||
if (!dayState.day || dayState.importing || running()?.kind === 'lunch') return;
|
||||
await closeAndStart(dayState.day.date, now, {
|
||||
kind: 'lunch',
|
||||
task: 'Pause déjeuner',
|
||||
client: null,
|
||||
}, running()?.id ?? null);
|
||||
await automateDay(dayState.day.date, settings, now, { lunch: true, expectedId: running()?.id ?? null });
|
||||
await refresh(now);
|
||||
}
|
||||
|
||||
@@ -193,6 +200,7 @@ export async function setLunch({ lunchType, restaurant }) {
|
||||
await patchActiveDay(dayState.day.date, () => ({
|
||||
lunchType,
|
||||
restaurant: lunchType === 'restaurant' ? (restaurant ?? null) : null,
|
||||
lunchManual: true,
|
||||
}));
|
||||
await refresh();
|
||||
}
|
||||
@@ -249,7 +257,7 @@ export async function applyImport(payload, mode) {
|
||||
export async function endDay(now = Date.now()) {
|
||||
if (!dayState.day || dayState.importing) return;
|
||||
const date = dayState.day.date;
|
||||
await closeDay(date, now);
|
||||
await automateDay(date, settings, now, { end: true });
|
||||
await refresh(now);
|
||||
}
|
||||
|
||||
@@ -293,7 +301,9 @@ export function predictNext(now = Date.now()) {
|
||||
const clients = suggestClients(model, now);
|
||||
|
||||
return {
|
||||
task: tasks[0]?.value ?? TRANSPORT_TASK,
|
||||
task: settings.autoPreviousTask && current?.kind === 'travel' && current.seq === 0
|
||||
? dayState.previousWork?.task ?? 'Travaux sur site'
|
||||
: tasks[0]?.value ?? TRANSPORT_TASK,
|
||||
client: current?.client ?? clients[0]?.value ?? null,
|
||||
tasks,
|
||||
clients,
|
||||
|
||||
Reference in New Issue
Block a user