A record may leave its lists some time after reaching a state: leaves_after
Test / test (pull_request) Successful in 1m20s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-30 20:50:28 +02:00
co-authored by Claude Opus 5.5
parent 96c513d912
commit 0dc25b4023
8 changed files with 147 additions and 1 deletions
+16 -1
View File
@@ -440,6 +440,12 @@ pub struct PlanNeed {
/// own it.
#[serde(default)]
pub one_per_person: bool,
/// How long a record stays in the lists once it reaches a stage,
/// when not for ever: `{ sluttet: { minutes: 10 } }` takes a card
/// off the desk's list ten minutes after it came off the front
/// page, instead of leaving it there greyed out.
#[serde(default)]
pub leaves_after: BTreeMap<String, crate::aggregates::LeavesSpec>,
pub collects: Vec<Field>,
/// How many required fields this person will tolerate: what the
/// business said about them, not what the form happens to ask.
@@ -763,6 +769,12 @@ impl Plan {
bail!(at(&format!("deadline on {state:?} goes to {:?}, which is not a move the state machine has from there", d.to)));
}
}
if let Some(bad) = need.leaves_after.keys().find(|st| !need.stages.contains(st)) {
bail!(at(&format!("leaves_after names {bad:?}, which is not one of its stages")));
}
if need.leaves_after.values().any(|l| l.millis() == 0) {
bail!(at("leaves_after needs some days, hours or minutes"));
}
if need.one_per_person && need.enters_as.is_none() {
bail!(at("one_per_person keeps a card for each signed-in person, and the need has no enters_as to sign them in"));
}
@@ -2059,6 +2071,9 @@ pub fn render(plan: &Plan, words: &BTreeMap<String, Value>) -> anyhow::Result<BT
if let Some(d) = need.deadlines.get(st) {
state["after"] = json!({ "days": d.days, "to": d.to });
}
if let Some(leaves) = need.leaves_after.get(st) {
state["leaves_after"] = json!(leaves);
}
(st.clone(), state)
}).collect::<Map<_, _>>(),
"transitions": machine(need),
@@ -2720,7 +2735,7 @@ mod tests {
stages: v(stages), done_when: v(done), success: v(success), skippable: v(skippable),
moves_by: BTreeMap::new(), watched_by: vec![],
events: BTreeMap::new(), attended_by: None, mails: BTreeMap::new(), stages_text: BTreeMap::new(), turned_down_from: vec![], reached_from: BTreeMap::new(), requires: String::new(), followed_by: vec![],
grants: BTreeMap::new(), deadlines: BTreeMap::new(), escalate: BTreeMap::new(), public_states: vec![], presents: false, paused: false, one_per_person: false,
grants: BTreeMap::new(), deadlines: BTreeMap::new(), escalate: BTreeMap::new(), public_states: vec![], presents: false, paused: false, one_per_person: false, leaves_after: BTreeMap::new(),
collects: vec![], max_fields: 3,
}
}
+5
View File
@@ -196,6 +196,11 @@ rest. Each is one line in the plan, and the builder does the wiring:
their own, which the form opens with and which sending again
replaces - a card on the front page they edit themselves. A picture
left unchosen stays as it was.
- **Let finished things go.** `leaves_after: { <stage>: { minutes: 10 } }`
takes a record out of every list that long after it reaches that
stage (days, hours and minutes add up). A card taken off the front
page need not stand greyed out on the desk for ever; the history
keeps it.
- **Pause what you cannot take on.** `paused: true` on a need keeps
its card on the page, saying what the business does and that it is
not taking new ones right now, with no form and a button that does