1use ifc_model::{Entity, EntityId, Transaction, Value};
16
17use crate::{AuthorError, AuthorResult};
18
19#[derive(Debug, Clone, Copy, Default)]
21pub struct PersonDraft<'a> {
22 pub identification: Option<&'a str>,
24 pub family_name: Option<&'a str>,
26 pub given_name: Option<&'a str>,
28}
29
30#[derive(Debug, Clone, Copy, Default)]
32pub struct OrganizationDraft<'a> {
33 pub identification: Option<&'a str>,
35 pub name: &'a str,
37 pub description: Option<&'a str>,
39}
40
41#[derive(Debug, Clone, Copy)]
43pub struct ApplicationDraft<'a> {
44 pub developer: EntityId,
46 pub version: &'a str,
48 pub full_name: &'a str,
50 pub identifier: &'a str,
52}
53
54pub fn add_person(tx: &mut Transaction, draft: PersonDraft<'_>) -> AuthorResult<EntityId> {
60 if draft.identification.is_none() && draft.family_name.is_none() && draft.given_name.is_none() {
61 return Err(AuthorError::MissingRequired {
62 entity: "IFCPERSON".to_owned(),
63 attribute: "Identification|FamilyName|GivenName".to_owned(),
64 });
65 }
66 Ok(tx.create(Entity::new(
67 "IFCPERSON",
68 vec![
69 opt_text(draft.identification),
70 opt_text(draft.family_name),
71 opt_text(draft.given_name),
72 Value::Null,
73 Value::Null,
74 Value::Null,
75 Value::Null,
76 Value::Null,
77 ],
78 )))
79}
80
81pub fn add_organization(
83 tx: &mut Transaction,
84 draft: OrganizationDraft<'_>,
85) -> AuthorResult<EntityId> {
86 if draft.name.trim().is_empty() {
87 return Err(AuthorError::MissingRequired {
88 entity: "IFCORGANIZATION".to_owned(),
89 attribute: "Name".to_owned(),
90 });
91 }
92 Ok(tx.create(Entity::new(
93 "IFCORGANIZATION",
94 vec![
95 opt_text(draft.identification),
96 Value::Text(draft.name.into()),
97 opt_text(draft.description),
98 Value::Null,
99 Value::Null,
100 ],
101 )))
102}
103
104pub fn add_person_and_organization(
107 tx: &mut Transaction,
108 person: EntityId,
109 organization: EntityId,
110) -> EntityId {
111 tx.create(Entity::new(
112 "IFCPERSONANDORGANIZATION",
113 vec![Value::Ref(person), Value::Ref(organization), Value::Null],
114 ))
115}
116
117pub fn add_application(
119 tx: &mut Transaction,
120 draft: ApplicationDraft<'_>,
121) -> AuthorResult<EntityId> {
122 for (attribute, value) in [
123 ("Version", draft.version),
124 ("ApplicationFullName", draft.full_name),
125 ("ApplicationIdentifier", draft.identifier),
126 ] {
127 if value.trim().is_empty() {
128 return Err(AuthorError::MissingRequired {
129 entity: "IFCAPPLICATION".to_owned(),
130 attribute: attribute.to_owned(),
131 });
132 }
133 }
134 Ok(tx.create(Entity::new(
135 "IFCAPPLICATION",
136 vec![
137 Value::Ref(draft.developer),
138 Value::Text(draft.version.into()),
139 Value::Text(draft.full_name.into()),
140 Value::Text(draft.identifier.into()),
141 ],
142 )))
143}
144
145#[derive(Debug, Clone, Copy)]
147pub struct OwnerHistoryDraft<'a> {
148 pub owning_user: EntityId,
150 pub owning_application: EntityId,
152 pub change_action: Option<&'a str>,
154 pub creation_date: i64,
157 pub last_modified_date: Option<i64>,
159}
160
161const CHANGE_ACTIONS: &[&str] = &["NOCHANGE", "MODIFIED", "ADDED", "DELETED", "NOTDEFINED"];
163
164pub fn add_owner_history(
172 tx: &mut Transaction,
173 draft: OwnerHistoryDraft<'_>,
174) -> AuthorResult<EntityId> {
175 if let Some(action) = draft.change_action {
176 if !CHANGE_ACTIONS
177 .iter()
178 .any(|known| known.eq_ignore_ascii_case(action))
179 {
180 return Err(AuthorError::TypeMismatch {
181 entity: "IFCOWNERHISTORY".to_owned(),
182 attribute: "ChangeAction".to_owned(),
183 expected: "an IfcChangeActionEnum constant".to_owned(),
184 found: action.to_owned(),
185 });
186 }
187 }
188 if let Some(modified) = draft.last_modified_date {
189 if modified < draft.creation_date {
190 return Err(AuthorError::TypeMismatch {
191 entity: "IFCOWNERHISTORY".to_owned(),
192 attribute: "LastModifiedDate".to_owned(),
193 expected: "a timestamp at or after CreationDate".to_owned(),
194 found: modified.to_string(),
195 });
196 }
197 }
198 Ok(tx.create(Entity::new(
199 "IFCOWNERHISTORY",
200 vec![
201 Value::Ref(draft.owning_user),
202 Value::Ref(draft.owning_application),
203 Value::Null,
204 draft
205 .change_action
206 .map_or(Value::Null, |a| Value::Enum(a.into())),
207 draft.last_modified_date.map_or(Value::Null, Value::Integer),
208 Value::Null,
209 Value::Null,
210 Value::Integer(draft.creation_date),
211 ],
212 )))
213}
214
215fn opt_text(value: Option<&str>) -> Value {
216 value.map_or(Value::Null, |v| Value::Text(v.into()))
217}