1use ifc_model::{Entity, EntityId, Model, Transaction, Value};
26
27use crate::calendar::{work_calendar_slot, work_time_slot};
28use crate::error::ScheduleAuthoringError;
29use crate::event::{event_slot, event_time_slot};
30use crate::query::{assigns_slot, nests_slot};
31use crate::schedule::work_control_slot as control_slot;
32use crate::schedule::WorkControlKind;
33use crate::sequence::lag_slot;
34use crate::sequence::relation::slot as sequence_slot;
35use crate::task::definition::task_slot;
36
37pub use datetime::{CalendarDate, DateTimeValue, LocalTime};
38
39pub type ScheduleAuthoringResult<T> = Result<T, ScheduleAuthoringError>;
41
42#[derive(Debug, Clone, Copy, Default)]
44#[non_exhaustive]
45pub struct TaskDraft<'a> {
46 pub global_id: &'a str,
48 pub name: Option<&'a str>,
50 pub description: Option<&'a str>,
52 pub identification: Option<&'a str>,
54 pub long_description: Option<&'a str>,
56 pub status: Option<&'a str>,
58 pub work_method: Option<&'a str>,
60 pub is_milestone: bool,
62 pub priority: Option<i64>,
64 pub task_time: Option<EntityId>,
66 pub predefined_type: Option<&'a str>,
68}
69
70pub fn create_task(
81 tx: &mut Transaction,
82 draft: TaskDraft<'_>,
83) -> ScheduleAuthoringResult<EntityId> {
84 checks::task(&draft)?;
85 let mut attributes = vec![Value::Null; task_slot::PREDEFINED_TYPE + 1];
86 attributes[task_slot::GLOBAL_ID] = Value::Text(draft.global_id.into());
87 attributes[task_slot::NAME] = optional_text(draft.name);
88 attributes[task_slot::DESCRIPTION] = optional_text(draft.description);
89 attributes[task_slot::IDENTIFICATION] = optional_text(draft.identification);
90 attributes[task_slot::LONG_DESCRIPTION] = optional_text(draft.long_description);
91 attributes[task_slot::STATUS] = optional_text(draft.status);
92 attributes[task_slot::WORK_METHOD] = optional_text(draft.work_method);
93 attributes[task_slot::IS_MILESTONE] = Value::Bool(draft.is_milestone);
94 attributes[task_slot::PRIORITY] = draft.priority.map_or(Value::Null, Value::Integer);
95 attributes[task_slot::TASK_TIME] = draft.task_time.map_or(Value::Null, Value::Ref);
96 attributes[task_slot::PREDEFINED_TYPE] = draft
97 .predefined_type
98 .map_or(Value::Null, |t| Value::Enum(t.into()));
99 Ok(tx.create(Entity::new("IFCTASK", attributes)))
100}
101
102pub(crate) fn optional_text(value: Option<&str>) -> Value {
103 value.map_or(Value::Null, |text| Value::Text(text.into()))
104}
105pub fn create_sequence(
117 tx: &mut Transaction,
118 global_id: &str,
119 predecessor: EntityId,
120 successor: EntityId,
121 sequence_type: Option<&str>,
122 time_lag: Option<EntityId>,
123) -> ScheduleAuthoringResult<EntityId> {
124 checks::sequence(global_id, predecessor, successor)?;
125 let mut attributes = vec![Value::Null; sequence_slot::SEQUENCE_TYPE + 2];
126 attributes[0] = Value::Text(global_id.into());
127 attributes[sequence_slot::RELATING] = Value::Ref(predecessor);
128 attributes[sequence_slot::RELATED] = Value::Ref(successor);
129 attributes[sequence_slot::TIME_LAG] = time_lag.map_or(Value::Null, Value::Ref);
130 attributes[sequence_slot::SEQUENCE_TYPE] =
131 sequence_type.map_or(Value::Null, |t| Value::Enum(t.into()));
132 Ok(tx.create(Entity::new("IFCRELSEQUENCE", attributes)))
133}
134
135#[derive(Debug, Clone, Copy, Default)]
144#[non_exhaustive]
145pub struct WorkControlDraft<'a> {
146 pub global_id: &'a str,
148 pub name: Option<&'a str>,
150 pub description: Option<&'a str>,
152 pub identification: Option<&'a str>,
154 pub creation_date: DateTimeValue<'a>,
157 pub purpose: Option<&'a str>,
159 pub duration: Option<&'a str>,
161 pub total_float: Option<&'a str>,
163 pub start_time: DateTimeValue<'a>,
166 pub finish_time: Option<DateTimeValue<'a>>,
169 pub predefined_type: Option<&'a str>,
171}
172
173pub fn create_work_control(
192 tx: &mut Transaction,
193 kind: WorkControlKind,
194 draft: WorkControlDraft<'_>,
195) -> ScheduleAuthoringResult<EntityId> {
196 let type_name = checks::work_control(kind, &draft)?;
197 let text = |attribute: &'static str, value: DateTimeValue<'_>| {
198 value.text().map(|text| Value::Text(text.into())).ok_or(
199 ScheduleAuthoringError::InvalidValue {
200 entity: type_name,
201 attribute,
202 expected: "ISO 8601 text: IfcDateTimeSelect records are IFC2X3 only",
203 },
204 )
205 };
206 let creation_date = text("CreationDate", draft.creation_date)?;
207 let start_time = text("StartTime", draft.start_time)?;
208 let finish_time = match draft.finish_time {
209 Some(value) => text("FinishTime", value)?,
210 None => Value::Null,
211 };
212 let mut attributes = vec![Value::Null; control_slot::PREDEFINED_TYPE + 1];
213 attributes[control_slot::GLOBAL_ID] = Value::Text(draft.global_id.into());
214 attributes[control_slot::NAME] = optional_text(draft.name);
215 attributes[control_slot::DESCRIPTION] = optional_text(draft.description);
216 attributes[control_slot::IDENTIFICATION] = optional_text(draft.identification);
217 attributes[control_slot::CREATION_DATE] = creation_date;
218 attributes[control_slot::PURPOSE] = optional_text(draft.purpose);
219 attributes[control_slot::DURATION] = optional_text(draft.duration);
220 attributes[control_slot::TOTAL_FLOAT] = optional_text(draft.total_float);
221 attributes[control_slot::START_TIME] = start_time;
222 attributes[control_slot::FINISH_TIME] = finish_time;
223 attributes[control_slot::PREDEFINED_TYPE] = draft
224 .predefined_type
225 .map_or(Value::Null, |t| Value::Enum(t.into()));
226 Ok(tx.create(Entity::new(type_name, attributes)))
227}
228
229pub fn assign_tasks_to_control(
245 tx: &mut Transaction,
246 global_id: &str,
247 control: EntityId,
248 tasks: &[EntityId],
249) -> ScheduleAuthoringResult<EntityId> {
250 checks::assignment(global_id, tasks)?;
251 let mut attributes = vec![Value::Null; assigns_slot::RELATING + 1];
252 attributes[assigns_slot::GLOBAL_ID] = Value::Text(global_id.into());
253 attributes[assigns_slot::RELATED] =
254 Value::List(tasks.iter().copied().map(Value::Ref).collect());
255 attributes[assigns_slot::RELATING] = Value::Ref(control);
256 Ok(tx.create(Entity::new("IFCRELASSIGNSTOCONTROL", attributes)))
257}
258
259pub fn nest_tasks(
277 tx: &mut Transaction,
278 global_id: &str,
279 parent: EntityId,
280 children: &[EntityId],
281) -> ScheduleAuthoringResult<EntityId> {
282 checks::nesting(global_id, parent, children)?;
283 let mut attributes = vec![Value::Null; nests_slot::RELATED + 1];
284 attributes[nests_slot::GLOBAL_ID] = Value::Text(global_id.into());
285 attributes[nests_slot::RELATING] = Value::Ref(parent);
286 attributes[nests_slot::RELATED] =
287 Value::List(children.iter().copied().map(Value::Ref).collect());
288 Ok(tx.create(Entity::new("IFCRELNESTS", attributes)))
289}
290
291pub fn create_work_time(
302 tx: &mut Transaction,
303 name: Option<&str>,
304 recurrence: Option<EntityId>,
305 start: Option<&str>,
306 finish: Option<&str>,
307) -> ScheduleAuthoringResult<EntityId> {
308 if name.is_some_and(|value| value.trim().is_empty()) {
309 return Err(ScheduleAuthoringError::InvalidValue {
310 entity: "IFCWORKTIME",
311 attribute: "Name",
312 expected: "a non-empty name when one is given",
313 });
314 }
315 let mut attributes = vec![Value::Null; work_time_slot::FINISH + 1];
316 attributes[work_time_slot::NAME] = optional_text(name);
317 attributes[work_time_slot::RECURRENCE_PATTERN] = recurrence.map_or(Value::Null, Value::Ref);
318 attributes[work_time_slot::START] = optional_text(start);
319 attributes[work_time_slot::FINISH] = optional_text(finish);
320 Ok(tx.create(Entity::new("IFCWORKTIME", attributes)))
321}
322
323pub fn create_work_calendar(
340 tx: &mut Transaction,
341 global_id: &str,
342 name: Option<&str>,
343 working_times: &[EntityId],
344 exception_times: &[EntityId],
345 predefined_type: Option<&str>,
346) -> ScheduleAuthoringResult<EntityId> {
347 checks::calendar(global_id, working_times, exception_times)?;
348 let mut attributes = vec![Value::Null; work_calendar_slot::PREDEFINED_TYPE + 1];
349 attributes[work_calendar_slot::GLOBAL_ID] = Value::Text(global_id.into());
350 attributes[work_calendar_slot::NAME] = optional_text(name);
351 attributes[work_calendar_slot::WORKING_TIMES] = reference_list(working_times);
352 attributes[work_calendar_slot::EXCEPTION_TIMES] = reference_list(exception_times);
353 attributes[work_calendar_slot::PREDEFINED_TYPE] =
354 predefined_type.map_or(Value::Null, |t| Value::Enum(t.into()));
355 Ok(tx.create(Entity::new("IFCWORKCALENDAR", attributes)))
356}
357
358pub(super) fn reference_list(ids: &[EntityId]) -> Value {
363 if ids.is_empty() {
364 return Value::Null;
365 }
366 Value::List(ids.iter().copied().map(Value::Ref).collect())
367}
368
369#[derive(Debug, Clone, Copy, Default)]
375#[non_exhaustive]
376pub struct EventDraft<'a> {
377 pub global_id: &'a str,
379 pub name: Option<&'a str>,
381 pub object_type: Option<&'a str>,
384 pub identification: Option<&'a str>,
386 pub long_description: Option<&'a str>,
388 pub predefined_type: Option<&'a str>,
390 pub trigger_type: Option<&'a str>,
392 pub user_defined_trigger_type: Option<&'a str>,
395 pub occurence_time: Option<EntityId>,
397}
398
399pub fn create_event(
412 tx: &mut Transaction,
413 draft: EventDraft<'_>,
414) -> ScheduleAuthoringResult<EntityId> {
415 checks::event(&draft)?;
416 let mut attributes = vec![Value::Null; event_slot::EVENT_OCCURENCE_TIME + 1];
417 attributes[event_slot::GLOBAL_ID] = Value::Text(draft.global_id.into());
418 attributes[event_slot::NAME] = optional_text(draft.name);
419 attributes[event_slot::OBJECT_TYPE] = optional_text(draft.object_type);
420 attributes[event_slot::IDENTIFICATION] = optional_text(draft.identification);
421 attributes[event_slot::LONG_DESCRIPTION] = optional_text(draft.long_description);
422 attributes[event_slot::PREDEFINED_TYPE] = optional_enum(draft.predefined_type);
423 attributes[event_slot::EVENT_TRIGGER_TYPE] = optional_enum(draft.trigger_type);
424 attributes[event_slot::USER_DEFINED_EVENT_TRIGGER_TYPE] =
425 optional_text(draft.user_defined_trigger_type);
426 attributes[event_slot::EVENT_OCCURENCE_TIME] =
427 draft.occurence_time.map_or(Value::Null, Value::Ref);
428 Ok(tx.create(Entity::new("IFCEVENT", attributes)))
429}
430
431pub(super) fn is_user_defined(value: Option<&str>) -> bool {
433 value.is_some_and(|v| v.eq_ignore_ascii_case("USERDEFINED"))
434}
435
436pub(super) fn blank(value: Option<&str>) -> bool {
441 value.is_none_or(|v| v.trim().is_empty())
442}
443
444pub(super) fn optional_enum(value: Option<&str>) -> Value {
446 value.map_or(Value::Null, |v| Value::Enum(v.into()))
447}
448
449#[derive(Debug, Clone, Copy, Default)]
454#[non_exhaustive]
455pub struct EventTimeDraft<'a> {
456 pub name: Option<&'a str>,
458 pub actual: Option<&'a str>,
460 pub early: Option<&'a str>,
462 pub late: Option<&'a str>,
464 pub schedule: Option<&'a str>,
466}
467
468pub fn create_event_time(
474 tx: &mut Transaction,
475 draft: EventTimeDraft<'_>,
476) -> ScheduleAuthoringResult<EntityId> {
477 let mut attributes = vec![Value::Null; event_time_slot::SCHEDULE_DATE + 1];
478 attributes[event_time_slot::NAME] = optional_text(draft.name);
479 attributes[event_time_slot::ACTUAL_DATE] = optional_text(draft.actual);
480 attributes[event_time_slot::EARLY_DATE] = optional_text(draft.early);
481 attributes[event_time_slot::LATE_DATE] = optional_text(draft.late);
482 attributes[event_time_slot::SCHEDULE_DATE] = optional_text(draft.schedule);
483 Ok(tx.create(Entity::new("IFCEVENTTIME", attributes)))
484}
485
486pub fn create_lag_time(
509 tx: &mut Transaction,
510 name: Option<&str>,
511 lag_value: Value,
512 duration_type: &str,
513) -> ScheduleAuthoringResult<EntityId> {
514 if duration_type.trim().is_empty() {
515 return Err(ScheduleAuthoringError::InvalidValue {
516 entity: "IFCLAGTIME",
517 attribute: "DurationType",
518 expected: "a non-empty IfcTaskDurationEnum value",
519 });
520 }
521 let (member, payload) = match lag_value {
523 Value::Typed { type_name, value } => (Some(type_name), *value),
524 bare => (None, bare),
525 };
526 let is = |name: &str| member.as_ref().is_none_or(|m| m.eq_ignore_ascii_case(name));
527 #[allow(clippy::cast_precision_loss)]
528 let (member, payload) = match payload {
529 Value::Text(text) if is("IFCDURATION") => ("IFCDURATION", Value::Text(text)),
530 Value::Real(ratio) if is("IFCRATIOMEASURE") => ("IFCRATIOMEASURE", Value::Real(ratio)),
531 Value::Integer(ratio) if is("IFCRATIOMEASURE") => {
532 ("IFCRATIOMEASURE", Value::Real(ratio as f64))
533 }
534 _ => {
535 return Err(ScheduleAuthoringError::InvalidValue {
536 entity: "IFCLAGTIME",
537 attribute: "LagValue",
538 expected: "a duration string or a ratio number",
539 })
540 }
541 };
542 let lag_value = Value::Typed {
543 type_name: member.into(),
544 value: Box::new(payload),
545 };
546 let mut attributes = vec![Value::Null; lag_slot::DURATION_TYPE + 1];
547 attributes[event_time_slot::NAME] = optional_text(name);
548 attributes[lag_slot::LAG_VALUE] = lag_value;
549 attributes[lag_slot::DURATION_TYPE] = Value::Enum(duration_type.into());
550 Ok(tx.create(Entity::new("IFCLAGTIME", attributes)))
551}
552
553pub fn create_lag_time_in(
566 tx: &mut Transaction,
567 model: &Model,
568 name: Option<&str>,
569 lag_value: Value,
570 duration_type: &str,
571) -> ScheduleAuthoringResult<EntityId> {
572 crate::release::bind(model)?.require_entity("IFCLAGTIME")?;
573 create_lag_time(tx, name, lag_value, duration_type)
574}
575
576mod builders;
577mod checks;
578mod datetime;
579mod owned;
580mod procedure;
581mod recurrence;
582mod timing;
583
584pub use owned::{
585 assign_tasks_to_control_with_owner_history, create_event_with_owner_history,
586 create_procedure_with_owner_history, create_sequence_with_owner_history,
587 create_task_with_owner_history, create_work_calendar_with_owner_history,
588 create_work_control_with_owner_history, nest_tasks_with_owner_history, TimeLag,
589};
590pub use procedure::{create_procedure, ProcedureDraft};
591pub use recurrence::{create_recurrence_pattern, create_recurrence_pattern_in, RecurrenceDraft};
592pub use timing::{
593 create_task_time, create_task_time_recurring, create_time_period, create_time_period_in,
594 TaskTimeDraft,
595};