use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::calendar::recurrence_slot;
use crate::calendar::{work_calendar_slot, work_time_slot};
use crate::error::ScheduleAuthoringError;
use crate::event::{event_slot, event_time_slot};
use crate::query::{assigns_slot, nests_slot};
use crate::schedule::work_control_slot as control_slot;
use crate::schedule::WorkControlKind;
use crate::sequence::lag_slot;
use crate::sequence::relation::slot as sequence_slot;
use crate::task::definition::task_slot;
pub type ScheduleAuthoringResult<T> = Result<T, ScheduleAuthoringError>;
#[derive(Debug, Clone, Copy, Default)]
pub struct TaskDraft<'a> {
pub global_id: &'a str,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub identification: Option<&'a str>,
pub long_description: Option<&'a str>,
pub status: Option<&'a str>,
pub work_method: Option<&'a str>,
pub is_milestone: bool,
pub priority: Option<i64>,
pub task_time: Option<EntityId>,
pub predefined_type: Option<&'a str>,
}
pub fn create_task(
tx: &mut Transaction,
draft: TaskDraft<'_>,
) -> ScheduleAuthoringResult<EntityId> {
checks::task(&draft)?;
let mut attributes = vec![Value::Null; task_slot::PREDEFINED_TYPE + 1];
attributes[task_slot::GLOBAL_ID] = Value::Text(draft.global_id.into());
attributes[task_slot::NAME] = optional_text(draft.name);
attributes[task_slot::DESCRIPTION] = optional_text(draft.description);
attributes[task_slot::IDENTIFICATION] = optional_text(draft.identification);
attributes[task_slot::LONG_DESCRIPTION] = optional_text(draft.long_description);
attributes[task_slot::STATUS] = optional_text(draft.status);
attributes[task_slot::WORK_METHOD] = optional_text(draft.work_method);
attributes[task_slot::IS_MILESTONE] = Value::Bool(draft.is_milestone);
attributes[task_slot::PRIORITY] = draft.priority.map_or(Value::Null, Value::Integer);
attributes[task_slot::TASK_TIME] = draft.task_time.map_or(Value::Null, Value::Ref);
attributes[task_slot::PREDEFINED_TYPE] = draft
.predefined_type
.map_or(Value::Null, |t| Value::Enum(t.into()));
Ok(tx.create(Entity::new("IFCTASK", attributes)))
}
pub(crate) fn optional_text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |text| Value::Text(text.into()))
}
pub fn create_sequence(
tx: &mut Transaction,
global_id: &str,
predecessor: EntityId,
successor: EntityId,
sequence_type: Option<&str>,
time_lag: Option<EntityId>,
) -> ScheduleAuthoringResult<EntityId> {
checks::sequence(global_id, predecessor, successor)?;
let mut attributes = vec![Value::Null; sequence_slot::SEQUENCE_TYPE + 2];
attributes[0] = Value::Text(global_id.into());
attributes[sequence_slot::RELATING] = Value::Ref(predecessor);
attributes[sequence_slot::RELATED] = Value::Ref(successor);
attributes[sequence_slot::TIME_LAG] = time_lag.map_or(Value::Null, Value::Ref);
attributes[sequence_slot::SEQUENCE_TYPE] =
sequence_type.map_or(Value::Null, |t| Value::Enum(t.into()));
Ok(tx.create(Entity::new("IFCRELSEQUENCE", attributes)))
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WorkControlDraft<'a> {
pub global_id: &'a str,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub identification: Option<&'a str>,
pub creation_date: &'a str,
pub purpose: Option<&'a str>,
pub duration: Option<&'a str>,
pub total_float: Option<&'a str>,
pub start_time: &'a str,
pub finish_time: Option<&'a str>,
pub predefined_type: Option<&'a str>,
}
pub fn create_work_control(
tx: &mut Transaction,
kind: WorkControlKind,
draft: WorkControlDraft<'_>,
) -> ScheduleAuthoringResult<EntityId> {
let type_name = checks::work_control(kind, &draft)?;
let mut attributes = vec![Value::Null; control_slot::PREDEFINED_TYPE + 1];
attributes[control_slot::GLOBAL_ID] = Value::Text(draft.global_id.into());
attributes[control_slot::NAME] = optional_text(draft.name);
attributes[control_slot::DESCRIPTION] = optional_text(draft.description);
attributes[control_slot::IDENTIFICATION] = optional_text(draft.identification);
attributes[control_slot::CREATION_DATE] = Value::Text(draft.creation_date.into());
attributes[control_slot::PURPOSE] = optional_text(draft.purpose);
attributes[control_slot::DURATION] = optional_text(draft.duration);
attributes[control_slot::TOTAL_FLOAT] = optional_text(draft.total_float);
attributes[control_slot::START_TIME] = Value::Text(draft.start_time.into());
attributes[control_slot::FINISH_TIME] = optional_text(draft.finish_time);
attributes[control_slot::PREDEFINED_TYPE] = draft
.predefined_type
.map_or(Value::Null, |t| Value::Enum(t.into()));
Ok(tx.create(Entity::new(type_name, attributes)))
}
pub fn assign_tasks_to_control(
tx: &mut Transaction,
global_id: &str,
control: EntityId,
tasks: &[EntityId],
) -> ScheduleAuthoringResult<EntityId> {
checks::assignment(global_id, tasks)?;
let mut attributes = vec![Value::Null; assigns_slot::RELATING + 1];
attributes[assigns_slot::GLOBAL_ID] = Value::Text(global_id.into());
attributes[assigns_slot::RELATED] =
Value::List(tasks.iter().copied().map(Value::Ref).collect());
attributes[assigns_slot::RELATING] = Value::Ref(control);
Ok(tx.create(Entity::new("IFCRELASSIGNSTOCONTROL", attributes)))
}
pub fn nest_tasks(
tx: &mut Transaction,
global_id: &str,
parent: EntityId,
children: &[EntityId],
) -> ScheduleAuthoringResult<EntityId> {
checks::nesting(global_id, parent, children)?;
let mut attributes = vec![Value::Null; nests_slot::RELATED + 1];
attributes[nests_slot::GLOBAL_ID] = Value::Text(global_id.into());
attributes[nests_slot::RELATING] = Value::Ref(parent);
attributes[nests_slot::RELATED] =
Value::List(children.iter().copied().map(Value::Ref).collect());
Ok(tx.create(Entity::new("IFCRELNESTS", attributes)))
}
pub fn create_work_time(
tx: &mut Transaction,
name: Option<&str>,
recurrence: Option<EntityId>,
start: Option<&str>,
finish: Option<&str>,
) -> ScheduleAuthoringResult<EntityId> {
if name.is_some_and(|value| value.trim().is_empty()) {
return Err(ScheduleAuthoringError::InvalidValue {
entity: "IFCWORKTIME",
attribute: "Name",
expected: "a non-empty name when one is given",
});
}
let mut attributes = vec![Value::Null; work_time_slot::FINISH + 1];
attributes[work_time_slot::NAME] = optional_text(name);
attributes[work_time_slot::RECURRENCE_PATTERN] = recurrence.map_or(Value::Null, Value::Ref);
attributes[work_time_slot::START] = optional_text(start);
attributes[work_time_slot::FINISH] = optional_text(finish);
Ok(tx.create(Entity::new("IFCWORKTIME", attributes)))
}
pub fn create_work_calendar(
tx: &mut Transaction,
global_id: &str,
name: Option<&str>,
working_times: &[EntityId],
exception_times: &[EntityId],
predefined_type: Option<&str>,
) -> ScheduleAuthoringResult<EntityId> {
checks::calendar(global_id, working_times, exception_times)?;
let mut attributes = vec![Value::Null; work_calendar_slot::PREDEFINED_TYPE + 1];
attributes[work_calendar_slot::GLOBAL_ID] = Value::Text(global_id.into());
attributes[work_calendar_slot::NAME] = optional_text(name);
attributes[work_calendar_slot::WORKING_TIMES] = reference_list(working_times);
attributes[work_calendar_slot::EXCEPTION_TIMES] = reference_list(exception_times);
attributes[work_calendar_slot::PREDEFINED_TYPE] =
predefined_type.map_or(Value::Null, |t| Value::Enum(t.into()));
Ok(tx.create(Entity::new("IFCWORKCALENDAR", attributes)))
}
pub(super) fn reference_list(ids: &[EntityId]) -> Value {
if ids.is_empty() {
return Value::Null;
}
Value::List(ids.iter().copied().map(Value::Ref).collect())
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EventDraft<'a> {
pub global_id: &'a str,
pub name: Option<&'a str>,
pub object_type: Option<&'a str>,
pub identification: Option<&'a str>,
pub long_description: Option<&'a str>,
pub predefined_type: Option<&'a str>,
pub trigger_type: Option<&'a str>,
pub user_defined_trigger_type: Option<&'a str>,
pub occurence_time: Option<EntityId>,
}
pub fn create_event(
tx: &mut Transaction,
draft: EventDraft<'_>,
) -> ScheduleAuthoringResult<EntityId> {
checks::event(&draft)?;
let mut attributes = vec![Value::Null; event_slot::EVENT_OCCURENCE_TIME + 1];
attributes[event_slot::GLOBAL_ID] = Value::Text(draft.global_id.into());
attributes[event_slot::NAME] = optional_text(draft.name);
attributes[event_slot::OBJECT_TYPE] = optional_text(draft.object_type);
attributes[event_slot::IDENTIFICATION] = optional_text(draft.identification);
attributes[event_slot::LONG_DESCRIPTION] = optional_text(draft.long_description);
attributes[event_slot::PREDEFINED_TYPE] = optional_enum(draft.predefined_type);
attributes[event_slot::EVENT_TRIGGER_TYPE] = optional_enum(draft.trigger_type);
attributes[event_slot::USER_DEFINED_EVENT_TRIGGER_TYPE] =
optional_text(draft.user_defined_trigger_type);
attributes[event_slot::EVENT_OCCURENCE_TIME] =
draft.occurence_time.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new("IFCEVENT", attributes)))
}
pub(super) fn is_user_defined(value: Option<&str>) -> bool {
value.is_some_and(|v| v.eq_ignore_ascii_case("USERDEFINED"))
}
pub(super) fn blank(value: Option<&str>) -> bool {
value.is_none_or(|v| v.trim().is_empty())
}
pub(super) fn optional_enum(value: Option<&str>) -> Value {
value.map_or(Value::Null, |v| Value::Enum(v.into()))
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EventTimeDraft<'a> {
pub name: Option<&'a str>,
pub actual: Option<&'a str>,
pub early: Option<&'a str>,
pub late: Option<&'a str>,
pub schedule: Option<&'a str>,
}
pub fn create_event_time(
tx: &mut Transaction,
draft: EventTimeDraft<'_>,
) -> ScheduleAuthoringResult<EntityId> {
let mut attributes = vec![Value::Null; event_time_slot::SCHEDULE_DATE + 1];
attributes[event_time_slot::NAME] = optional_text(draft.name);
attributes[event_time_slot::ACTUAL_DATE] = optional_text(draft.actual);
attributes[event_time_slot::EARLY_DATE] = optional_text(draft.early);
attributes[event_time_slot::LATE_DATE] = optional_text(draft.late);
attributes[event_time_slot::SCHEDULE_DATE] = optional_text(draft.schedule);
Ok(tx.create(Entity::new("IFCEVENTTIME", attributes)))
}
pub fn create_lag_time(
tx: &mut Transaction,
name: Option<&str>,
lag_value: Value,
duration_type: &str,
) -> ScheduleAuthoringResult<EntityId> {
if duration_type.trim().is_empty() {
return Err(ScheduleAuthoringError::InvalidValue {
entity: "IFCLAGTIME",
attribute: "DurationType",
expected: "a non-empty IfcTaskDurationEnum value",
});
}
let (member, payload) = match lag_value {
Value::Typed { type_name, value } => (Some(type_name), *value),
bare => (None, bare),
};
let is = |name: &str| member.as_ref().is_none_or(|m| m.eq_ignore_ascii_case(name));
#[allow(clippy::cast_precision_loss)]
let (member, payload) = match payload {
Value::Text(text) if is("IFCDURATION") => ("IFCDURATION", Value::Text(text)),
Value::Real(ratio) if is("IFCRATIOMEASURE") => ("IFCRATIOMEASURE", Value::Real(ratio)),
Value::Integer(ratio) if is("IFCRATIOMEASURE") => {
("IFCRATIOMEASURE", Value::Real(ratio as f64))
}
_ => {
return Err(ScheduleAuthoringError::InvalidValue {
entity: "IFCLAGTIME",
attribute: "LagValue",
expected: "a duration string or a ratio number",
})
}
};
let lag_value = Value::Typed {
type_name: member.into(),
value: Box::new(payload),
};
let mut attributes = vec![Value::Null; lag_slot::DURATION_TYPE + 1];
attributes[event_time_slot::NAME] = optional_text(name);
attributes[lag_slot::LAG_VALUE] = lag_value;
attributes[lag_slot::DURATION_TYPE] = Value::Enum(duration_type.into());
Ok(tx.create(Entity::new("IFCLAGTIME", attributes)))
}
#[derive(Debug, Clone, Default)]
pub struct RecurrenceDraft<'a> {
pub recurrence_type: &'a str,
pub days: Vec<i64>,
pub weekdays: Vec<i64>,
pub months: Vec<i64>,
pub position: Option<i64>,
pub interval: Option<i64>,
pub occurrences: Option<i64>,
}
pub fn create_recurrence_pattern(
tx: &mut Transaction,
draft: &RecurrenceDraft<'_>,
) -> ScheduleAuthoringResult<EntityId> {
if draft.recurrence_type.trim().is_empty() {
return Err(ScheduleAuthoringError::InvalidValue {
entity: "IFCRECURRENCEPATTERN",
attribute: "RecurrenceType",
expected: "a non-empty IfcRecurrenceTypeEnum value",
});
}
check_range(&draft.days, 1, 31, "DayComponent")?;
check_range(&draft.weekdays, 1, 7, "WeekdayComponent")?;
check_range(&draft.months, 1, 12, "MonthComponent")?;
check_positive(draft.interval, "Interval")?;
check_positive(draft.occurrences, "Occurrences")?;
let mut attributes = vec![Value::Null; recurrence_slot::OCCURRENCES + 1];
attributes[recurrence_slot::RECURRENCE_TYPE] = Value::Enum(draft.recurrence_type.into());
attributes[recurrence_slot::DAY_COMPONENT] = integer_list(&draft.days);
attributes[recurrence_slot::WEEKDAY_COMPONENT] = integer_list(&draft.weekdays);
attributes[recurrence_slot::MONTH_COMPONENT] = integer_list(&draft.months);
attributes[recurrence_slot::POSITION] = draft.position.map_or(Value::Null, Value::Integer);
attributes[recurrence_slot::INTERVAL] = draft.interval.map_or(Value::Null, Value::Integer);
attributes[recurrence_slot::OCCURRENCES] =
draft.occurrences.map_or(Value::Null, Value::Integer);
Ok(tx.create(Entity::new("IFCRECURRENCEPATTERN", attributes)))
}
fn check_range(
values: &[i64],
low: i64,
high: i64,
attribute: &'static str,
) -> ScheduleAuthoringResult<()> {
if values.iter().any(|v| !(low..=high).contains(v)) {
return Err(ScheduleAuthoringError::InvalidValue {
entity: "IFCRECURRENCEPATTERN",
attribute,
expected: "components within the schema's 1-based range",
});
}
Ok(())
}
fn check_positive(value: Option<i64>, attribute: &'static str) -> ScheduleAuthoringResult<()> {
if value.is_some_and(|v| v <= 0) {
return Err(ScheduleAuthoringError::InvalidValue {
entity: "IFCRECURRENCEPATTERN",
attribute,
expected: "a positive count",
});
}
Ok(())
}
fn integer_list(values: &[i64]) -> Value {
if values.is_empty() {
return Value::Null;
}
Value::List(values.iter().copied().map(Value::Integer).collect())
}
mod checks;
mod owned;
mod procedure;
mod timing;
pub use owned::{
assign_tasks_to_control_with_owner_history, create_event_with_owner_history,
create_procedure_with_owner_history, create_sequence_with_owner_history,
create_task_with_owner_history, create_work_calendar_with_owner_history,
create_work_control_with_owner_history, nest_tasks_with_owner_history,
};
pub use procedure::{create_procedure, ProcedureDraft};
pub use timing::{create_task_time, create_task_time_recurring, create_time_period, TaskTimeDraft};