build: turned_down_from, and uhhm's stages in words
A need may say which unfinished stages a case can still be turned down from; the machine no longer offers the bad end from every stage. uhhm's collaborators are declined before they start and depart after, as the live site has it. The uhhm plan gets stages_text and three desk buttons reworded: every uhhm desk scores full marks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
aeff4c8cbe
commit
6e49694211
+25
-2
@@ -171,6 +171,11 @@ pub struct PlanNeed {
|
||||
/// were complete).
|
||||
#[serde(default)]
|
||||
pub skippable: Vec<String>,
|
||||
/// The unfinished stages a case may still be turned down from;
|
||||
/// empty means every one. An applicant is declined before they
|
||||
/// start and not after, where they depart instead.
|
||||
#[serde(default)]
|
||||
pub turned_down_from: Vec<String>,
|
||||
/// Who presses the button that moves a case INTO a stage, where it
|
||||
/// is not `handled_by`: another group, several groups, or
|
||||
/// `submitter` - the person who sent the form, through their own
|
||||
@@ -351,6 +356,11 @@ impl Plan {
|
||||
if need.watched_by.iter().any(|g| g == PUBLIC) && need.enters_as.is_some() {
|
||||
bail!(at("a public view is for what the public sent; an internal need has its group's desk"));
|
||||
}
|
||||
for state in &need.turned_down_from {
|
||||
if need.done_when.contains(state) || !need.stages.contains(state) {
|
||||
bail!(at(&format!("turned_down_from names {state:?}, which is not one of its unfinished stages")));
|
||||
}
|
||||
}
|
||||
for state in need.done_when.iter().chain(&need.skippable) {
|
||||
if !need.stages.contains(state) {
|
||||
bail!(at(&format!("{state:?} is not one of its stages")));
|
||||
@@ -441,6 +451,7 @@ impl Plan {
|
||||
|| other.attended_by != first.attended_by
|
||||
|| other.mails != first.mails
|
||||
|| other.stages_text != first.stages_text
|
||||
|| other.turned_down_from != first.turned_down_from
|
||||
{
|
||||
bail!("needs {:?} and {:?} both land in {bucket:?} and must agree on stages, done_when, skippable, events, attended_by, mails and who moves and watches it", first.id, other.id);
|
||||
}
|
||||
@@ -516,7 +527,9 @@ pub fn machine(need: &PlanNeed) -> BTreeMap<String, Vec<String>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
to.extend(ends_badly.iter().map(|s| (*s).clone()));
|
||||
if need.turned_down_from.is_empty() || need.turned_down_from.contains(stage) {
|
||||
to.extend(ends_badly.iter().map(|s| (*s).clone()));
|
||||
}
|
||||
moves.insert((*stage).clone(), to);
|
||||
}
|
||||
moves
|
||||
@@ -1642,7 +1655,7 @@ mod tests {
|
||||
lands_in: "b".into(), handled_by: "g".into(),
|
||||
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(),
|
||||
events: BTreeMap::new(), attended_by: None, mails: BTreeMap::new(), stages_text: BTreeMap::new(), turned_down_from: vec![],
|
||||
collects: vec![], max_fields: 3,
|
||||
}
|
||||
}
|
||||
@@ -1720,6 +1733,16 @@ mod tests {
|
||||
assert!(!m.contains_key("won"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_case_is_turned_down_only_where_the_plan_says() {
|
||||
let mut n = need(&["open", "invited", "active", "departed", "declined"], &["departed", "declined"], &["departed"], &[]);
|
||||
n.turned_down_from = vec!["open".into(), "invited".into()];
|
||||
let m = machine(&n);
|
||||
assert_eq!(m["open"], ["invited", "declined"]);
|
||||
assert_eq!(m["invited"], ["active", "declined"]);
|
||||
assert_eq!(m["active"], ["departed"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_skippable_stage_can_be_passed_over() {
|
||||
let m = machine(&need(&["open", "awaiting_papers", "approved", "declined"], &["approved", "declined"], &["approved"], &["awaiting_papers"]));
|
||||
|
||||
Reference in New Issue
Block a user