1use ifc_model::{Entity, EntityId, Transaction, Value};
16
17use crate::{AuthorError, AuthorResult};
18
19#[derive(Debug, Clone, Copy, Default)]
21#[non_exhaustive]
22pub struct PersonDraft<'a> {
23 pub identification: Option<&'a str>,
25 pub family_name: Option<&'a str>,
27 pub given_name: Option<&'a str>,
29}
30
31impl<'a> PersonDraft<'a> {
32 #[must_use]
34 pub fn new() -> Self {
35 Self {
36 identification: None,
37 family_name: None,
38 given_name: None,
39 }
40 }
41
42 #[must_use]
44 pub fn identification(mut self, value: &'a str) -> Self {
45 self.identification = Some(value);
46 self
47 }
48
49 #[must_use]
51 pub fn family_name(mut self, value: &'a str) -> Self {
52 self.family_name = Some(value);
53 self
54 }
55
56 #[must_use]
58 pub fn given_name(mut self, value: &'a str) -> Self {
59 self.given_name = Some(value);
60 self
61 }
62}
63
64#[derive(Debug, Clone, Copy, Default)]
66#[non_exhaustive]
67pub struct OrganizationDraft<'a> {
68 pub identification: Option<&'a str>,
70 pub name: &'a str,
72 pub description: Option<&'a str>,
74}
75
76impl<'a> OrganizationDraft<'a> {
77 #[must_use]
79 pub fn new(name: &'a str) -> Self {
80 Self {
81 identification: None,
82 name,
83 description: None,
84 }
85 }
86
87 #[must_use]
89 pub fn identification(mut self, value: &'a str) -> Self {
90 self.identification = Some(value);
91 self
92 }
93
94 #[must_use]
96 pub fn description(mut self, value: &'a str) -> Self {
97 self.description = Some(value);
98 self
99 }
100}
101
102#[derive(Debug, Clone, Copy)]
104#[non_exhaustive]
105pub struct ApplicationDraft<'a> {
106 pub developer: EntityId,
108 pub version: &'a str,
110 pub full_name: &'a str,
112 pub identifier: &'a str,
114}
115
116impl<'a> ApplicationDraft<'a> {
117 #[must_use]
119 pub fn new(
120 developer: EntityId,
121 version: &'a str,
122 full_name: &'a str,
123 identifier: &'a str,
124 ) -> Self {
125 Self {
126 developer,
127 version,
128 full_name,
129 identifier,
130 }
131 }
132}
133
134pub fn add_person(tx: &mut Transaction, draft: PersonDraft<'_>) -> AuthorResult<EntityId> {
140 if draft.identification.is_none() && draft.family_name.is_none() && draft.given_name.is_none() {
141 return Err(AuthorError::MissingRequired {
142 entity: "IFCPERSON".to_owned(),
143 attribute: "Identification|FamilyName|GivenName".to_owned(),
144 });
145 }
146 Ok(tx.create(Entity::new(
147 "IFCPERSON",
148 vec![
149 opt_text(draft.identification),
150 opt_text(draft.family_name),
151 opt_text(draft.given_name),
152 Value::Null,
153 Value::Null,
154 Value::Null,
155 Value::Null,
156 Value::Null,
157 ],
158 )))
159}
160
161pub fn add_organization(
163 tx: &mut Transaction,
164 draft: OrganizationDraft<'_>,
165) -> AuthorResult<EntityId> {
166 if draft.name.trim().is_empty() {
167 return Err(AuthorError::MissingRequired {
168 entity: "IFCORGANIZATION".to_owned(),
169 attribute: "Name".to_owned(),
170 });
171 }
172 Ok(tx.create(Entity::new(
173 "IFCORGANIZATION",
174 vec![
175 opt_text(draft.identification),
176 Value::Text(draft.name.into()),
177 opt_text(draft.description),
178 Value::Null,
179 Value::Null,
180 ],
181 )))
182}
183
184pub fn add_person_and_organization(
187 tx: &mut Transaction,
188 person: EntityId,
189 organization: EntityId,
190) -> EntityId {
191 tx.create(Entity::new(
192 "IFCPERSONANDORGANIZATION",
193 vec![Value::Ref(person), Value::Ref(organization), Value::Null],
194 ))
195}
196
197pub fn add_application(
199 tx: &mut Transaction,
200 draft: ApplicationDraft<'_>,
201) -> AuthorResult<EntityId> {
202 for (attribute, value) in [
203 ("Version", draft.version),
204 ("ApplicationFullName", draft.full_name),
205 ("ApplicationIdentifier", draft.identifier),
206 ] {
207 if value.trim().is_empty() {
208 return Err(AuthorError::MissingRequired {
209 entity: "IFCAPPLICATION".to_owned(),
210 attribute: attribute.to_owned(),
211 });
212 }
213 }
214 Ok(tx.create(Entity::new(
215 "IFCAPPLICATION",
216 vec![
217 Value::Ref(draft.developer),
218 Value::Text(draft.version.into()),
219 Value::Text(draft.full_name.into()),
220 Value::Text(draft.identifier.into()),
221 ],
222 )))
223}
224
225#[derive(Debug, Clone, Copy)]
227#[non_exhaustive]
228pub struct OwnerHistoryDraft<'a> {
229 pub owning_user: EntityId,
231 pub owning_application: EntityId,
233 pub change_action: Option<&'a str>,
235 pub creation_date: i64,
238 pub last_modified_date: Option<i64>,
240}
241
242impl<'a> OwnerHistoryDraft<'a> {
243 #[must_use]
245 pub fn new(owning_user: EntityId, owning_application: EntityId, creation_date: i64) -> Self {
246 Self {
247 owning_user,
248 owning_application,
249 change_action: None,
250 creation_date,
251 last_modified_date: None,
252 }
253 }
254
255 #[must_use]
257 pub fn change_action(mut self, value: &'a str) -> Self {
258 self.change_action = Some(value);
259 self
260 }
261
262 #[must_use]
264 pub fn last_modified_date(mut self, value: i64) -> Self {
265 self.last_modified_date = Some(value);
266 self
267 }
268}
269
270const CHANGE_ACTIONS: &[&str] = &["NOCHANGE", "MODIFIED", "ADDED", "DELETED", "NOTDEFINED"];
272
273pub fn add_owner_history(
281 tx: &mut Transaction,
282 draft: OwnerHistoryDraft<'_>,
283) -> AuthorResult<EntityId> {
284 if let Some(action) = draft.change_action {
285 if !CHANGE_ACTIONS
286 .iter()
287 .any(|known| known.eq_ignore_ascii_case(action))
288 {
289 return Err(AuthorError::TypeMismatch {
290 entity: "IFCOWNERHISTORY".to_owned(),
291 attribute: "ChangeAction".to_owned(),
292 expected: "an IfcChangeActionEnum constant".to_owned(),
293 found: action.to_owned(),
294 });
295 }
296 }
297 if let Some(modified) = draft.last_modified_date {
298 if modified < draft.creation_date {
299 return Err(AuthorError::TypeMismatch {
300 entity: "IFCOWNERHISTORY".to_owned(),
301 attribute: "LastModifiedDate".to_owned(),
302 expected: "a timestamp at or after CreationDate".to_owned(),
303 found: modified.to_string(),
304 });
305 }
306 }
307 Ok(tx.create(Entity::new(
308 "IFCOWNERHISTORY",
309 vec![
310 Value::Ref(draft.owning_user),
311 Value::Ref(draft.owning_application),
312 Value::Null,
313 draft
314 .change_action
315 .map_or(Value::Null, |a| Value::Enum(a.into())),
316 draft.last_modified_date.map_or(Value::Null, Value::Integer),
317 Value::Null,
318 Value::Null,
319 Value::Integer(draft.creation_date),
320 ],
321 )))
322}
323
324fn opt_text(value: Option<&str>) -> Value {
325 value.map_or(Value::Null, |v| Value::Text(v.into()))
326}