ifc_schedule/authoring/
timing.rs1use ifc_model::{Edit, Entity, EntityId, Model, Transaction, Value};
7
8use super::{optional_text, ScheduleAuthoringResult};
9use crate::error::ScheduleAuthoringError;
10use crate::task::definition::time_slot;
11
12#[derive(Debug, Clone, Copy, Default)]
18pub struct TaskTimeDraft<'a> {
19 pub name: Option<&'a str>,
21 pub duration_type: Option<&'a str>,
23 pub schedule_duration: Option<&'a str>,
25 pub schedule_start: Option<&'a str>,
27 pub schedule_finish: Option<&'a str>,
29 pub actual_start: Option<&'a str>,
31 pub actual_finish: Option<&'a str>,
33 pub is_critical: Option<bool>,
35 pub completion: Option<f64>,
37}
38
39fn task_time_attributes(
45 entity: &'static str,
46 draft: TaskTimeDraft<'_>,
47) -> ScheduleAuthoringResult<Vec<Value>> {
48 if let Some(completion) = draft.completion {
49 if !completion.is_finite() || !(0.0..=100.0).contains(&completion) {
50 return Err(ScheduleAuthoringError::InvalidValue {
51 entity,
52 attribute: "Completion",
53 expected: "a percentage in 0..=100",
54 });
55 }
56 }
57 if let Some(kind) = draft.duration_type {
58 if !["WORKTIME", "ELAPSEDTIME", "NOTDEFINED"]
59 .iter()
60 .any(|known| known.eq_ignore_ascii_case(kind))
61 {
62 return Err(ScheduleAuthoringError::InvalidValue {
63 entity,
64 attribute: "DurationType",
65 expected: "WORKTIME, ELAPSEDTIME or NOTDEFINED",
66 });
67 }
68 }
69 let mut attributes = vec![Value::Null; time_slot::COMPLETION + 1];
70 attributes[0] = optional_text(draft.name);
71 attributes[time_slot::DURATION_TYPE] = draft
72 .duration_type
73 .map_or(Value::Null, |t| Value::Enum(t.into()));
74 attributes[time_slot::SCHEDULE_DURATION] = optional_text(draft.schedule_duration);
75 attributes[time_slot::SCHEDULE_START] = optional_text(draft.schedule_start);
76 attributes[time_slot::SCHEDULE_FINISH] = optional_text(draft.schedule_finish);
77 attributes[time_slot::ACTUAL_START] = optional_text(draft.actual_start);
78 attributes[time_slot::ACTUAL_FINISH] = optional_text(draft.actual_finish);
79 attributes[time_slot::IS_CRITICAL] = draft.is_critical.map_or(Value::Null, Value::Bool);
80 attributes[time_slot::COMPLETION] = draft.completion.map_or(Value::Null, Value::Real);
81 Ok(attributes)
82}
83
84pub fn create_task_time(
90 tx: &mut Transaction,
91 draft: TaskTimeDraft<'_>,
92) -> ScheduleAuthoringResult<EntityId> {
93 let attributes = task_time_attributes("IFCTASKTIME", draft)?;
94 Ok(tx.create(Entity::new("IFCTASKTIME", attributes)))
95}
96
97pub fn create_task_time_recurring(
109 tx: &mut Transaction,
110 model: &Model,
111 draft: TaskTimeDraft<'_>,
112 recurrence: EntityId,
113) -> ScheduleAuthoringResult<EntityId> {
114 const ENTITY: &str = "IFCTASKTIMERECURRING";
115 const RECURRENCE_SLOT: usize = 20;
116 let pattern_is_valid = tx
117 .edits()
118 .iter()
119 .rev()
120 .find_map(|edit| match edit {
121 Edit::Create { id, entity } if *id == recurrence => {
122 Some(entity.type_name.as_ref().to_owned())
123 }
124 _ => None,
125 })
126 .or_else(|| {
127 model
128 .get(recurrence)
129 .map(|entity| entity.type_name.as_ref().to_owned())
130 })
131 .is_some_and(|name| name.eq_ignore_ascii_case("IFCRECURRENCEPATTERN"));
132 if !pattern_is_valid {
133 return Err(ScheduleAuthoringError::InvalidValue {
134 entity: ENTITY,
135 attribute: "Recurrence",
136 expected: "an IfcRecurrencePattern",
137 });
138 }
139 let mut attributes = task_time_attributes(ENTITY, draft)?;
140 attributes.resize(RECURRENCE_SLOT + 1, Value::Null);
141 attributes[RECURRENCE_SLOT] = Value::Ref(recurrence);
142 Ok(tx.create(Entity::new(ENTITY, attributes)))
143}
144
145pub fn create_time_period(
157 tx: &mut Transaction,
158 start_time: &str,
159 end_time: &str,
160) -> ScheduleAuthoringResult<EntityId> {
161 const ENTITY: &str = "IFCTIMEPERIOD";
162 for (attribute, value) in [("StartTime", start_time), ("EndTime", end_time)] {
163 if value.trim().is_empty() {
164 return Err(ScheduleAuthoringError::InvalidValue {
165 entity: ENTITY,
166 attribute,
167 expected: "a non-empty ISO 8601 time",
168 });
169 }
170 }
171 Ok(tx.create(Entity::new(
172 ENTITY,
173 vec![Value::Text(start_time.into()), Value::Text(end_time.into())],
174 )))
175}