use ifc_model::{Entity, EntityId, Model, Value};
pub(crate) mod task_slot {
pub const GLOBAL_ID: usize = 0;
pub const NAME: usize = 2;
pub const DESCRIPTION: usize = 3;
pub const IDENTIFICATION: usize = 5;
pub const LONG_DESCRIPTION: usize = 6;
pub const STATUS: usize = 7;
pub const WORK_METHOD: usize = 8;
pub const IS_MILESTONE: usize = 9;
pub const PRIORITY: usize = 10;
pub const TASK_TIME: usize = 11;
pub const PREDEFINED_TYPE: usize = 12;
}
pub(crate) mod time_slot {
pub const DURATION_TYPE: usize = 3;
pub const SCHEDULE_DURATION: usize = 4;
pub const SCHEDULE_START: usize = 5;
pub const SCHEDULE_FINISH: usize = 6;
pub const EARLY_START: usize = 7;
pub const LATE_FINISH: usize = 10;
pub const FREE_FLOAT: usize = 11;
pub const TOTAL_FLOAT: usize = 12;
pub const IS_CRITICAL: usize = 13;
pub const ACTUAL_DURATION: usize = 15;
pub const ACTUAL_START: usize = 16;
pub const ACTUAL_FINISH: usize = 17;
pub const COMPLETION: usize = 19;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DurationType {
ElapsedTime,
WorkTime,
NotDefined,
}
impl DurationType {
fn parse(token: &str) -> Option<Self> {
Some(match token {
"ELAPSEDTIME" => Self::ElapsedTime,
"WORKTIME" => Self::WorkTime,
"NOTDEFINED" => Self::NotDefined,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaskTimeAnomaly {
MilestoneWithDuration {
task: EntityId,
time: EntityId,
duration: String,
},
NotATaskTime {
task: EntityId,
target: EntityId,
found: String,
},
}
#[derive(Debug, Clone, Copy)]
pub struct TaskTime<'m> {
id: EntityId,
entity: &'m Entity,
}
impl<'m> TaskTime<'m> {
#[must_use]
pub fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub fn duration_type(&self) -> Option<DurationType> {
match self.entity.attribute(time_slot::DURATION_TYPE)? {
Value::Enum(token) => DurationType::parse(token),
_ => None,
}
}
#[must_use]
pub fn schedule_duration(&self) -> Option<&'m str> {
self.entity.text(time_slot::SCHEDULE_DURATION)
}
#[must_use]
pub fn schedule_start(&self) -> Option<&'m str> {
self.entity.text(time_slot::SCHEDULE_START)
}
#[must_use]
pub fn schedule_finish(&self) -> Option<&'m str> {
self.entity.text(time_slot::SCHEDULE_FINISH)
}
#[must_use]
pub fn early_start(&self) -> Option<&'m str> {
self.entity.text(time_slot::EARLY_START)
}
#[must_use]
pub fn late_finish(&self) -> Option<&'m str> {
self.entity.text(time_slot::LATE_FINISH)
}
#[must_use]
pub fn free_float(&self) -> Option<&'m str> {
self.entity.text(time_slot::FREE_FLOAT)
}
#[must_use]
pub fn total_float(&self) -> Option<&'m str> {
self.entity.text(time_slot::TOTAL_FLOAT)
}
#[must_use]
pub fn is_critical(&self) -> Option<bool> {
self.entity.attribute(time_slot::IS_CRITICAL)?.as_bool()
}
#[must_use]
pub fn actual_start(&self) -> Option<&'m str> {
self.entity.text(time_slot::ACTUAL_START)
}
#[must_use]
pub fn actual_finish(&self) -> Option<&'m str> {
self.entity.text(time_slot::ACTUAL_FINISH)
}
#[must_use]
pub fn actual_duration(&self) -> Option<&'m str> {
self.entity.text(time_slot::ACTUAL_DURATION)
}
#[must_use]
pub fn completion(&self) -> Option<f64> {
self.entity
.attribute(time_slot::COMPLETION)?
.unwrap_typed()
.as_f64()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Task<'m> {
id: EntityId,
entity: &'m Entity,
}
impl<'m> Task<'m> {
#[must_use]
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self { id, entity }
}
#[must_use]
pub fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub fn global_id(&self) -> Option<&'m str> {
self.entity.text(task_slot::GLOBAL_ID)
}
#[must_use]
pub fn name(&self) -> Option<&'m str> {
self.entity.text(task_slot::NAME)
}
#[must_use]
pub fn description(&self) -> Option<&'m str> {
self.entity.text(task_slot::DESCRIPTION)
}
#[must_use]
pub fn identification(&self) -> Option<&'m str> {
self.entity.text(task_slot::IDENTIFICATION)
}
#[must_use]
pub fn long_description(&self) -> Option<&'m str> {
self.entity.text(task_slot::LONG_DESCRIPTION)
}
#[must_use]
pub fn status(&self) -> Option<&'m str> {
self.entity.text(task_slot::STATUS)
}
#[must_use]
pub fn work_method(&self) -> Option<&'m str> {
self.entity.text(task_slot::WORK_METHOD)
}
#[must_use]
pub fn is_milestone(&self) -> Option<bool> {
self.entity.attribute(task_slot::IS_MILESTONE)?.as_bool()
}
#[must_use]
pub fn priority(&self) -> Option<i64> {
self.entity.attribute(task_slot::PRIORITY)?.as_i64()
}
#[must_use]
pub fn predefined_type(&self) -> Option<&'m str> {
match self.entity.attribute(task_slot::PREDEFINED_TYPE)? {
Value::Enum(token) => Some(token),
_ => None,
}
}
#[must_use]
pub fn task_time_ref(&self) -> Option<EntityId> {
match self.entity.attribute(task_slot::TASK_TIME)? {
Value::Ref(id) => Some(*id),
_ => None,
}
}
#[must_use]
pub fn time(&self, model: &'m Model) -> (Option<TaskTime<'m>>, Vec<TaskTimeAnomaly>) {
let mut anomalies = Vec::new();
let Some(target) = self.task_time_ref() else {
return (None, anomalies);
};
let Some(entity) = model.get(target) else {
return (None, anomalies);
};
if !entity.type_name.eq_ignore_ascii_case("IFCTASKTIME") {
anomalies.push(TaskTimeAnomaly::NotATaskTime {
task: self.id,
target,
found: entity.type_name.to_string(),
});
return (None, anomalies);
}
let time = TaskTime { id: target, entity };
if self.is_milestone() == Some(true) {
if let Some(duration) = time.schedule_duration() {
anomalies.push(TaskTimeAnomaly::MilestoneWithDuration {
task: self.id,
time: target,
duration: duration.to_string(),
});
}
}
(Some(time), anomalies)
}
}
#[must_use]
pub fn tasks(model: &Model) -> Vec<Task<'_>> {
model
.of_type("IFCTASK")
.map(|(id, entity)| Task::new(id, entity))
.collect()
}