use ifc_model::{EntityId, Value};
use crate::author::editor::{build_entity, refs, text, validate_enum, ResourceEditor};
use crate::error::{ResourceError, ResourceResult};
const ACTOR_SELECT: &[&str] = &["IfcOrganization", "IfcPerson", "IfcPersonAndOrganization"];
#[derive(Debug, Clone, Copy)]
pub struct ActorDraft<'a> {
pub global_id: &'a str,
pub the_actor: EntityId,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub predefined_type: Option<&'a str>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AssetDraft<'a> {
pub global_id: &'a str,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub identification: Option<&'a str>,
pub original_value: Option<EntityId>,
pub current_value: Option<EntityId>,
pub total_replacement_cost: Option<EntityId>,
pub owner: Option<EntityId>,
pub user: Option<EntityId>,
pub responsible_person: Option<EntityId>,
pub incorporation_date: Option<&'a str>,
pub depreciated_value: Option<EntityId>,
}
impl ResourceEditor<'_> {
pub fn create_actor(&mut self, draft: ActorDraft<'_>) -> ResourceResult<EntityId> {
let occupant = draft.predefined_type.is_some();
let entity_type = if occupant { "IfcOccupant" } else { "IfcActor" };
self.validate_new_global_id(draft.global_id)?;
self.check_reference_select(
draft.the_actor,
"TheActor",
"IfcActorSelect",
ACTOR_SELECT,
draft.the_actor,
)?;
if let Some(value) = draft.predefined_type {
validate_enum(self.schema, entity_type, "PredefinedType", value)?;
if value == "USERDEFINED"
&& draft
.object_type
.is_none_or(|value| value.trim().is_empty())
{
return Err(ResourceError::SemanticViolation {
entity: None,
rule: "USERDEFINED_REQUIRES_OBJECT_TYPE",
});
}
}
let entity = build_entity(
self.schema,
entity_type,
&[
("GlobalId", Some(text(draft.global_id))),
("Name", draft.name.map(text)),
("Description", draft.description.map(text)),
("ObjectType", draft.object_type.map(text)),
("TheActor", Some(Value::Ref(draft.the_actor))),
(
"PredefinedType",
draft.predefined_type.map(|t| Value::Enum(t.into())),
),
],
)?;
self.commit_create(entity)
}
pub fn create_asset(&mut self, draft: AssetDraft<'_>) -> ResourceResult<EntityId> {
self.validate_new_global_id(draft.global_id)?;
for (attribute, target) in [
("OriginalValue", draft.original_value),
("CurrentValue", draft.current_value),
("TotalReplacementCost", draft.total_replacement_cost),
("DepreciatedValue", draft.depreciated_value),
] {
if let Some(target) = target {
self.check_reference(target, attribute, "IfcCostValue", target)?;
}
}
for (attribute, target) in [("Owner", draft.owner), ("User", draft.user)] {
if let Some(target) = target {
self.check_reference_select(
target,
attribute,
"IfcActorSelect",
ACTOR_SELECT,
target,
)?;
}
}
if let Some(person) = draft.responsible_person {
self.check_reference(person, "ResponsiblePerson", "IfcPerson", person)?;
}
let entity = build_entity(
self.schema,
"IfcAsset",
&[
("GlobalId", Some(text(draft.global_id))),
("Name", draft.name.map(text)),
("Description", draft.description.map(text)),
("ObjectType", draft.object_type.map(text)),
("Identification", draft.identification.map(text)),
("OriginalValue", draft.original_value.map(Value::Ref)),
("CurrentValue", draft.current_value.map(Value::Ref)),
(
"TotalReplacementCost",
draft.total_replacement_cost.map(Value::Ref),
),
("Owner", draft.owner.map(Value::Ref)),
("User", draft.user.map(Value::Ref)),
(
"ResponsiblePerson",
draft.responsible_person.map(Value::Ref),
),
("IncorporationDate", draft.incorporation_date.map(text)),
("DepreciatedValue", draft.depreciated_value.map(Value::Ref)),
],
)?;
self.commit_create(entity)
}
pub fn create_inventory(
&mut self,
draft: InventoryDraft<'_>,
responsible_persons: &[EntityId],
) -> ResourceResult<EntityId> {
const ENTITY: &str = "IfcInventory";
self.validate_new_global_id(draft.global_id)?;
if let Some(token) = draft.predefined_type {
validate_enum(self.schema, ENTITY, "PredefinedType", token)?;
if token.eq_ignore_ascii_case("USERDEFINED")
&& draft.object_type.is_none_or(|text| text.trim().is_empty())
{
return Err(ResourceError::SemanticViolation {
entity: None,
rule: "USERDEFINED_REQUIRES_OBJECT_TYPE",
});
}
}
if let Some(target) = draft.jurisdiction {
self.check_reference_select(
target,
"Jurisdiction",
"IfcActorSelect",
ACTOR_SELECT,
target,
)?;
}
for (attribute, target) in [
("CurrentValue", draft.current_value),
("OriginalValue", draft.original_value),
] {
if let Some(target) = target {
self.check_reference(target, attribute, "IfcCostValue", target)?;
}
}
if responsible_persons.is_empty() {
return Err(ResourceError::InvalidDraft {
entity_type: ENTITY,
attribute: "ResponsiblePersons",
expected: "at least one person, per SET [1:?]",
});
}
for person in responsible_persons {
self.check_reference(*person, "ResponsiblePersons", "IfcPerson", *person)?;
}
let entity = build_entity(
self.schema,
ENTITY,
&[
("GlobalId", Some(text(draft.global_id))),
("Name", draft.name.map(text)),
("Description", draft.description.map(text)),
("ObjectType", draft.object_type.map(text)),
(
"PredefinedType",
draft.predefined_type.map(|t| Value::Enum(t.into())),
),
("Jurisdiction", draft.jurisdiction.map(Value::Ref)),
("ResponsiblePersons", Some(refs(responsible_persons))),
("LastUpdateDate", draft.last_update_date.map(text)),
("CurrentValue", draft.current_value.map(Value::Ref)),
("OriginalValue", draft.original_value.map(Value::Ref)),
],
)?;
self.commit_create(entity)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct InventoryDraft<'a> {
pub global_id: &'a str,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub predefined_type: Option<&'a str>,
pub jurisdiction: Option<EntityId>,
pub last_update_date: Option<&'a str>,
pub current_value: Option<EntityId>,
pub original_value: Option<EntityId>,
}