Fuellarbeit: zeitlich flexible Arbeit fuellt ungenutztes Budget (#717, Stufe 1)
Eine einmalige Aufgabe mit fill_by (spaetestens bis) statt Termin laeuft, wenn der Tag Budget uebrig laesst: nach regulaerer und nachgeholter Arbeit, nur wenn nichts vom Agenten wartet, bevorzugt ab 18 Uhr, und nur wenn ihre eigene Schaetzung in den Spielraum aus #716 passt. Kurz vor der Frist wird einmal ein Mensch gefragt; nach der Frist laeuft sie als regulaere Arbeit. Ehrliche Schaetzung (TaskCostEstimator): eigene Laeufe, dann Geschwister unter derselben Elternaufgabe, dann die Angabe des Autors (als Schaetzung gekennzeichnet), zuletzt das Mittel des Agenten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
25f74d9ad6
commit
183e922278
@@ -363,6 +363,12 @@
|
||||
"tasks.origin": "Herkunft",
|
||||
"tasks.originTask": "bei der Arbeit an {id}",
|
||||
"tasks.originRun": "Lauf öffnen",
|
||||
"tasks.fillWork": "Füllarbeit: läuft, wenn das Budget Platz hat, spätestens bis {time}",
|
||||
"tasks.fillAlerted": "bisher kein Platz gefunden, Mensch gefragt am {time}",
|
||||
"tasks.estimate": "Schätzung des Auftraggebers: {usd} je Lauf (nicht gemessen)",
|
||||
"tasks.fillBy": "Füllarbeit, spätestens bis",
|
||||
"tasks.fillByHint": "Leer lassen für reguläre Arbeit. Füllarbeit läuft auf Budget, das der Tag übrig lässt; ein Datum meint das Ende dieses Tages.",
|
||||
"tasks.estimateUsd": "Schätzung je Lauf (USD)",
|
||||
|
||||
"services.title": "Dienste",
|
||||
"services.newService": "Neuer Dienst",
|
||||
|
||||
@@ -363,6 +363,12 @@
|
||||
"tasks.origin": "Arose from",
|
||||
"tasks.originTask": "while working on {id}",
|
||||
"tasks.originRun": "open run",
|
||||
"tasks.fillWork": "Fill work: runs when the budget has room, at the latest by {time}",
|
||||
"tasks.fillAlerted": "no room found so far, a human was asked on {time}",
|
||||
"tasks.estimate": "Author's estimate: {usd} per run (not measured)",
|
||||
"tasks.fillBy": "Fill work, at the latest by",
|
||||
"tasks.fillByHint": "Leave empty for regular work. Fill work runs on budget the day leaves unused; a date means the end of that day.",
|
||||
"tasks.estimateUsd": "Estimate per run (USD)",
|
||||
|
||||
"services.title": "Services",
|
||||
"services.newService": "New Service",
|
||||
|
||||
@@ -174,6 +174,14 @@ function brettAuswahl(tasks) {
|
||||
erstelleElement("span", { text: t("tasks.board") }), auswahl);
|
||||
}
|
||||
|
||||
/**
|
||||
* #717: The date for the input field. A date is stored as the end of that day (the next
|
||||
* midnight, UTC), so one millisecond earlier is the day that was meant.
|
||||
*/
|
||||
function fuellDatum(iso) {
|
||||
return new Date(new Date(iso).getTime() - 1).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function terminText(task) {
|
||||
if (!task.when) return t("tasks.scheduleNone");
|
||||
const art = task.when.kind;
|
||||
@@ -233,6 +241,19 @@ function erstelleAufgabenKarte(task) {
|
||||
zeilen.push(erstelleElement("p", { klasse: "text-klein", text: `⚠ ${task.hinweis}` }));
|
||||
}
|
||||
|
||||
// #717: Fill work waits for room, not for a time — said on the card, or it looks forgotten.
|
||||
if (task.fillBy) {
|
||||
const teile = [t("tasks.fillWork", { time: formatiereDatumZeit(task.fillBy) })];
|
||||
if (task.fillAlertedAt) teile.push(t("tasks.fillAlerted", { time: formatiereDatumZeit(task.fillAlertedAt) }));
|
||||
zeilen.push(erstelleElement("p", { klasse: "gedaempft text-klein", text: `◔ ${teile.join(" · ")}` }));
|
||||
}
|
||||
if (task.estimateUsd != null) {
|
||||
zeilen.push(erstelleElement("p", {
|
||||
klasse: "gedaempft text-klein",
|
||||
text: t("tasks.estimate", { usd: formatiereKosten(task.estimateUsd) })
|
||||
}));
|
||||
}
|
||||
|
||||
// #716: What the budget refused, and what happens next.
|
||||
const ausfall = task.budgetMiss;
|
||||
if (ausfall) {
|
||||
@@ -360,6 +381,17 @@ async function oeffneAufgabenDialog(task) {
|
||||
// #714: The task this one serves — the run is told the chain up to the unit's goal.
|
||||
const eltern = erstelleElement("input", { type: "text", value: task?.parentId || "", placeholder: "t-…" });
|
||||
|
||||
// #717: Fill work — a deadline instead of an appointment — and the author's estimate.
|
||||
const fuellBis = erstelleElement("input", {
|
||||
type: "date",
|
||||
value: task?.fillBy ? fuellDatum(task.fillBy) : "",
|
||||
title: t("tasks.fillByHint")
|
||||
});
|
||||
const schaetzung = erstelleElement("input", {
|
||||
type: "number", min: "0", step: "0.01",
|
||||
value: task?.estimateUsd != null ? String(task.estimateUsd) : ""
|
||||
});
|
||||
|
||||
const hinweis = erstelleElement("p", { klasse: "gedaempft text-klein" });
|
||||
|
||||
const aktualisiere = () => {
|
||||
@@ -371,6 +403,9 @@ async function oeffneAufgabenDialog(task) {
|
||||
const ohneTermin = terminArt.value === "";
|
||||
terminWert.disabled = ohneTermin;
|
||||
zeitzone.disabled = ohneTermin;
|
||||
// Fill work is one-time; a recurring schedule and a deadline contradict each other.
|
||||
fuellBis.disabled = wiederkehrend;
|
||||
if (wiederkehrend) fuellBis.value = "";
|
||||
terminWert.placeholder =
|
||||
{ cron: "e.g. 0 7-22/3 * * *", every: "e.g. 30m, 2h, 1d", at: "e.g. 2026-09-15T08:00" }[terminArt.value] || "";
|
||||
};
|
||||
@@ -387,6 +422,8 @@ async function oeffneAufgabenDialog(task) {
|
||||
erstelleElement("label", { text: "Timezone:" }), zeitzone,
|
||||
erstelleElement("label", { text: "Acceptance Criteria:" }), abnahme,
|
||||
erstelleElement("label", { text: t("tasks.parent") + ":" }), eltern,
|
||||
erstelleElement("label", { text: t("tasks.fillBy") + ":", title: t("tasks.fillByHint") }), fuellBis,
|
||||
erstelleElement("label", { text: t("tasks.estimateUsd") + ":" }), schaetzung,
|
||||
erstelleElement("span"), hinweis
|
||||
);
|
||||
|
||||
@@ -418,7 +455,9 @@ async function oeffneAufgabenDialog(task) {
|
||||
timeZone: terminArt.value ? zeitzone.value.trim() : null,
|
||||
acceptance: abnahme.value.trim(),
|
||||
priority: task?.priority ?? 3,
|
||||
parentId: eltern.value.trim()
|
||||
parentId: eltern.value.trim(),
|
||||
fillBy: fuellBis.value || null,
|
||||
estimateUsd: schaetzung.value === "" ? null : Number(schaetzung.value)
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user