use ifc_model::{Entity, EntityId, Model};
use crate::error::ScheduleReadError;
use crate::release::ReadRelease;
use crate::SchemaVersion;
const TASK: &str = "IFCTASK";
const TASK_TIME: &str = "IFCTASKTIME";
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 IS_CRITICAL: usize = 13;
pub const ACTUAL_START: usize = 16;
pub const ACTUAL_FINISH: usize = 17;
pub const COMPLETION: usize = 19;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
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)]
#[non_exhaustive]
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,
release: ReadRelease,
}
impl<'m> TaskTime<'m> {
#[must_use]
pub fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub fn duration_type(&self) -> Option<DurationType> {
DurationType::parse(self.release.token(TASK_TIME, self.entity, "DurationType")?)
}
#[must_use]
pub fn schedule_duration(&self) -> Option<&'m str> {
self.release
.text(TASK_TIME, self.entity, "ScheduleDuration")
}
#[must_use]
pub fn schedule_start(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "ScheduleStart")
}
#[must_use]
pub fn schedule_finish(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "ScheduleFinish")
}
#[must_use]
pub fn early_start(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "EarlyStart")
}
#[must_use]
pub fn late_finish(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "LateFinish")
}
#[must_use]
pub fn free_float(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "FreeFloat")
}
#[must_use]
pub fn total_float(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "TotalFloat")
}
#[must_use]
pub fn is_critical(&self) -> Option<bool> {
self.release
.value(TASK_TIME, self.entity, "IsCritical")?
.as_bool()
}
#[must_use]
pub fn actual_start(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "ActualStart")
}
#[must_use]
pub fn actual_finish(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "ActualFinish")
}
#[must_use]
pub fn actual_duration(&self) -> Option<&'m str> {
self.release.text(TASK_TIME, self.entity, "ActualDuration")
}
#[must_use]
pub fn completion(&self) -> Option<f64> {
self.release
.value(TASK_TIME, self.entity, "Completion")?
.unwrap_typed()
.as_f64()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Task<'m> {
id: EntityId,
entity: &'m Entity,
release: ReadRelease,
}
impl<'m> Task<'m> {
pub fn new(
id: EntityId,
entity: &'m Entity,
release: SchemaVersion,
) -> Result<Self, ScheduleReadError> {
Ok(Self::bound(id, entity, ReadRelease::of_version(release)?))
}
pub(crate) const fn bound(id: EntityId, entity: &'m Entity, release: ReadRelease) -> Self {
Self {
id,
entity,
release,
}
}
#[must_use]
pub fn release(&self) -> SchemaVersion {
self.release.version()
}
fn text(&self, attribute: &'static str) -> Option<&'m str> {
self.release.text(TASK, self.entity, attribute)
}
#[must_use]
pub fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub fn global_id(&self) -> Option<&'m str> {
self.text("GlobalId")
}
#[must_use]
pub fn name(&self) -> Option<&'m str> {
self.text("Name")
}
#[must_use]
pub fn description(&self) -> Option<&'m str> {
self.text("Description")
}
#[must_use]
pub fn identification(&self) -> Option<&'m str> {
self.text("Identification")
}
#[must_use]
pub fn long_description(&self) -> Option<&'m str> {
self.text("LongDescription")
}
#[must_use]
pub fn status(&self) -> Option<&'m str> {
self.text("Status")
}
#[must_use]
pub fn work_method(&self) -> Option<&'m str> {
self.text("WorkMethod")
}
#[must_use]
pub fn is_milestone(&self) -> Option<bool> {
self.release
.value(TASK, self.entity, "IsMilestone")?
.as_bool()
}
#[must_use]
pub fn priority(&self) -> Option<i64> {
self.release.value(TASK, self.entity, "Priority")?.as_i64()
}
#[must_use]
pub fn predefined_type(&self) -> Option<&'m str> {
self.release.token(TASK, self.entity, "PredefinedType")
}
#[must_use]
pub fn task_time_ref(&self) -> Option<EntityId> {
self.release.reference(TASK, self.entity, "TaskTime")
}
#[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,
release: self.release,
};
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)
}
}
pub fn tasks(model: &Model) -> Result<Vec<Task<'_>>, ScheduleReadError> {
let release = ReadRelease::of(model)?;
Ok(model
.of_type(TASK)
.map(|(id, entity)| Task::bound(id, entity, release))
.collect())
}