use ifc_model::{Edit, Entity, EntityId, Model, Transaction, Value};
use super::{optional_text, ScheduleAuthoringResult};
use crate::error::ScheduleAuthoringError;
use crate::task::definition::time_slot;
#[derive(Debug, Clone, Copy, Default)]
pub struct TaskTimeDraft<'a> {
pub name: Option<&'a str>,
pub duration_type: Option<&'a str>,
pub schedule_duration: Option<&'a str>,
pub schedule_start: Option<&'a str>,
pub schedule_finish: Option<&'a str>,
pub actual_start: Option<&'a str>,
pub actual_finish: Option<&'a str>,
pub is_critical: Option<bool>,
pub completion: Option<f64>,
}
fn task_time_attributes(
entity: &'static str,
draft: TaskTimeDraft<'_>,
) -> ScheduleAuthoringResult<Vec<Value>> {
if let Some(completion) = draft.completion {
if !completion.is_finite() || !(0.0..=100.0).contains(&completion) {
return Err(ScheduleAuthoringError::InvalidValue {
entity,
attribute: "Completion",
expected: "a percentage in 0..=100",
});
}
}
if let Some(kind) = draft.duration_type {
if !["WORKTIME", "ELAPSEDTIME", "NOTDEFINED"]
.iter()
.any(|known| known.eq_ignore_ascii_case(kind))
{
return Err(ScheduleAuthoringError::InvalidValue {
entity,
attribute: "DurationType",
expected: "WORKTIME, ELAPSEDTIME or NOTDEFINED",
});
}
}
let mut attributes = vec![Value::Null; time_slot::COMPLETION + 1];
attributes[0] = optional_text(draft.name);
attributes[time_slot::DURATION_TYPE] = draft
.duration_type
.map_or(Value::Null, |t| Value::Enum(t.into()));
attributes[time_slot::SCHEDULE_DURATION] = optional_text(draft.schedule_duration);
attributes[time_slot::SCHEDULE_START] = optional_text(draft.schedule_start);
attributes[time_slot::SCHEDULE_FINISH] = optional_text(draft.schedule_finish);
attributes[time_slot::ACTUAL_START] = optional_text(draft.actual_start);
attributes[time_slot::ACTUAL_FINISH] = optional_text(draft.actual_finish);
attributes[time_slot::IS_CRITICAL] = draft.is_critical.map_or(Value::Null, Value::Bool);
attributes[time_slot::COMPLETION] = draft.completion.map_or(Value::Null, Value::Real);
Ok(attributes)
}
pub fn create_task_time(
tx: &mut Transaction,
draft: TaskTimeDraft<'_>,
) -> ScheduleAuthoringResult<EntityId> {
let attributes = task_time_attributes("IFCTASKTIME", draft)?;
Ok(tx.create(Entity::new("IFCTASKTIME", attributes)))
}
pub fn create_task_time_recurring(
tx: &mut Transaction,
model: &Model,
draft: TaskTimeDraft<'_>,
recurrence: EntityId,
) -> ScheduleAuthoringResult<EntityId> {
const ENTITY: &str = "IFCTASKTIMERECURRING";
const RECURRENCE_SLOT: usize = 20;
let pattern_is_valid = tx
.edits()
.iter()
.rev()
.find_map(|edit| match edit {
Edit::Create { id, entity } if *id == recurrence => {
Some(entity.type_name.as_ref().to_owned())
}
_ => None,
})
.or_else(|| {
model
.get(recurrence)
.map(|entity| entity.type_name.as_ref().to_owned())
})
.is_some_and(|name| name.eq_ignore_ascii_case("IFCRECURRENCEPATTERN"));
if !pattern_is_valid {
return Err(ScheduleAuthoringError::InvalidValue {
entity: ENTITY,
attribute: "Recurrence",
expected: "an IfcRecurrencePattern",
});
}
let mut attributes = task_time_attributes(ENTITY, draft)?;
attributes.resize(RECURRENCE_SLOT + 1, Value::Null);
attributes[RECURRENCE_SLOT] = Value::Ref(recurrence);
Ok(tx.create(Entity::new(ENTITY, attributes)))
}
pub fn create_time_period(
tx: &mut Transaction,
start_time: &str,
end_time: &str,
) -> ScheduleAuthoringResult<EntityId> {
const ENTITY: &str = "IFCTIMEPERIOD";
for (attribute, value) in [("StartTime", start_time), ("EndTime", end_time)] {
if value.trim().is_empty() {
return Err(ScheduleAuthoringError::InvalidValue {
entity: ENTITY,
attribute,
expected: "a non-empty ISO 8601 time",
});
}
}
Ok(tx.create(Entity::new(
ENTITY,
vec![Value::Text(start_time.into()), Value::Text(end_time.into())],
)))
}