1use ifc_model::{EntityId, Value};
18
19use crate::author::editor::{build_entity, refs, text, validate_enum, ResourceEditor};
20use crate::error::{ResourceError, ResourceResult};
21
22const ACTOR_SELECT: &[&str] = &["IfcOrganization", "IfcPerson", "IfcPersonAndOrganization"];
28
29#[derive(Debug, Clone, Copy)]
31#[non_exhaustive]
32pub struct ActorDraft<'a> {
33 pub global_id: &'a str,
35 pub the_actor: EntityId,
40 pub name: Option<&'a str>,
42 pub description: Option<&'a str>,
44 pub object_type: Option<&'a str>,
46 pub predefined_type: Option<&'a str>,
51}
52
53impl<'a> ActorDraft<'a> {
54 #[must_use]
56 pub fn new(global_id: &'a str, the_actor: EntityId) -> Self {
57 Self {
58 global_id,
59 the_actor,
60 name: None,
61 description: None,
62 object_type: None,
63 predefined_type: None,
64 }
65 }
66
67 #[must_use]
69 pub fn name(mut self, value: &'a str) -> Self {
70 self.name = Some(value);
71 self
72 }
73
74 #[must_use]
76 pub fn description(mut self, value: &'a str) -> Self {
77 self.description = Some(value);
78 self
79 }
80
81 #[must_use]
83 pub fn object_type(mut self, value: &'a str) -> Self {
84 self.object_type = Some(value);
85 self
86 }
87
88 #[must_use]
90 pub fn predefined_type(mut self, value: &'a str) -> Self {
91 self.predefined_type = Some(value);
92 self
93 }
94}
95
96#[derive(Debug, Clone, Copy, Default)]
102#[non_exhaustive]
103pub struct AssetDraft<'a> {
104 pub global_id: &'a str,
106 pub name: Option<&'a str>,
108 pub description: Option<&'a str>,
110 pub object_type: Option<&'a str>,
112 pub identification: Option<&'a str>,
114 pub original_value: Option<EntityId>,
116 pub current_value: Option<EntityId>,
118 pub total_replacement_cost: Option<EntityId>,
120 pub owner: Option<EntityId>,
122 pub user: Option<EntityId>,
124 pub responsible_person: Option<EntityId>,
126 pub incorporation_date: Option<&'a str>,
128 pub depreciated_value: Option<EntityId>,
130}
131
132impl<'a> AssetDraft<'a> {
133 #[must_use]
135 pub fn new(global_id: &'a str) -> Self {
136 Self {
137 global_id,
138 name: None,
139 description: None,
140 object_type: None,
141 identification: None,
142 original_value: None,
143 current_value: None,
144 total_replacement_cost: None,
145 owner: None,
146 user: None,
147 responsible_person: None,
148 incorporation_date: None,
149 depreciated_value: None,
150 }
151 }
152
153 #[must_use]
155 pub fn name(mut self, value: &'a str) -> Self {
156 self.name = Some(value);
157 self
158 }
159
160 #[must_use]
162 pub fn description(mut self, value: &'a str) -> Self {
163 self.description = Some(value);
164 self
165 }
166
167 #[must_use]
169 pub fn object_type(mut self, value: &'a str) -> Self {
170 self.object_type = Some(value);
171 self
172 }
173
174 #[must_use]
176 pub fn identification(mut self, value: &'a str) -> Self {
177 self.identification = Some(value);
178 self
179 }
180
181 #[must_use]
183 pub fn original_value(mut self, value: EntityId) -> Self {
184 self.original_value = Some(value);
185 self
186 }
187
188 #[must_use]
190 pub fn current_value(mut self, value: EntityId) -> Self {
191 self.current_value = Some(value);
192 self
193 }
194
195 #[must_use]
197 pub fn total_replacement_cost(mut self, value: EntityId) -> Self {
198 self.total_replacement_cost = Some(value);
199 self
200 }
201
202 #[must_use]
204 pub fn owner(mut self, value: EntityId) -> Self {
205 self.owner = Some(value);
206 self
207 }
208
209 #[must_use]
211 pub fn user(mut self, value: EntityId) -> Self {
212 self.user = Some(value);
213 self
214 }
215
216 #[must_use]
218 pub fn responsible_person(mut self, value: EntityId) -> Self {
219 self.responsible_person = Some(value);
220 self
221 }
222
223 #[must_use]
225 pub fn incorporation_date(mut self, value: &'a str) -> Self {
226 self.incorporation_date = Some(value);
227 self
228 }
229
230 #[must_use]
232 pub fn depreciated_value(mut self, value: EntityId) -> Self {
233 self.depreciated_value = Some(value);
234 self
235 }
236}
237
238impl ResourceEditor<'_> {
239 pub fn create_actor(&mut self, draft: ActorDraft<'_>) -> ResourceResult<EntityId> {
253 let occupant = draft.predefined_type.is_some();
254 let entity_type = if occupant { "IfcOccupant" } else { "IfcActor" };
255 self.validate_new_global_id(draft.global_id)?;
256 self.check_reference_select(
257 draft.the_actor,
258 "TheActor",
259 "IfcActorSelect",
260 ACTOR_SELECT,
261 draft.the_actor,
262 )?;
263 if let Some(value) = draft.predefined_type {
264 validate_enum(self.schema, entity_type, "PredefinedType", value)?;
265 if value == "USERDEFINED"
266 && draft
267 .object_type
268 .is_none_or(|value| value.trim().is_empty())
269 {
270 return Err(ResourceError::SemanticViolation {
271 entity: None,
272 rule: "USERDEFINED_REQUIRES_OBJECT_TYPE",
273 });
274 }
275 }
276 let entity = build_entity(
277 self.schema,
278 entity_type,
279 &[
280 ("GlobalId", Some(text(draft.global_id))),
281 ("Name", draft.name.map(text)),
282 ("Description", draft.description.map(text)),
283 ("ObjectType", draft.object_type.map(text)),
284 ("TheActor", Some(Value::Ref(draft.the_actor))),
285 (
286 "PredefinedType",
287 draft.predefined_type.map(|t| Value::Enum(t.into())),
288 ),
289 ],
290 )?;
291 self.commit_create(entity)
292 }
293
294 pub fn create_asset(&mut self, draft: AssetDraft<'_>) -> ResourceResult<EntityId> {
307 self.validate_new_global_id(draft.global_id)?;
308 for (attribute, target) in [
309 ("OriginalValue", draft.original_value),
310 ("CurrentValue", draft.current_value),
311 ("TotalReplacementCost", draft.total_replacement_cost),
312 ("DepreciatedValue", draft.depreciated_value),
313 ] {
314 if let Some(target) = target {
315 self.check_reference(target, attribute, "IfcCostValue", target)?;
316 }
317 }
318 for (attribute, target) in [("Owner", draft.owner), ("User", draft.user)] {
319 if let Some(target) = target {
320 self.check_reference_select(
321 target,
322 attribute,
323 "IfcActorSelect",
324 ACTOR_SELECT,
325 target,
326 )?;
327 }
328 }
329 if let Some(person) = draft.responsible_person {
330 self.check_reference(person, "ResponsiblePerson", "IfcPerson", person)?;
331 }
332 let entity = build_entity(
333 self.schema,
334 "IfcAsset",
335 &[
336 ("GlobalId", Some(text(draft.global_id))),
337 ("Name", draft.name.map(text)),
338 ("Description", draft.description.map(text)),
339 ("ObjectType", draft.object_type.map(text)),
340 ("Identification", draft.identification.map(text)),
341 ("OriginalValue", draft.original_value.map(Value::Ref)),
342 ("CurrentValue", draft.current_value.map(Value::Ref)),
343 (
344 "TotalReplacementCost",
345 draft.total_replacement_cost.map(Value::Ref),
346 ),
347 ("Owner", draft.owner.map(Value::Ref)),
348 ("User", draft.user.map(Value::Ref)),
349 (
350 "ResponsiblePerson",
351 draft.responsible_person.map(Value::Ref),
352 ),
353 ("IncorporationDate", draft.incorporation_date.map(text)),
354 ("DepreciatedValue", draft.depreciated_value.map(Value::Ref)),
355 ],
356 )?;
357 self.commit_create(entity)
358 }
359
360 pub fn create_inventory(
370 &mut self,
371 draft: InventoryDraft<'_>,
372 responsible_persons: &[EntityId],
373 ) -> ResourceResult<EntityId> {
374 const ENTITY: &str = "IfcInventory";
375 self.validate_new_global_id(draft.global_id)?;
376 if let Some(token) = draft.predefined_type {
377 validate_enum(self.schema, ENTITY, "PredefinedType", token)?;
378 if token.eq_ignore_ascii_case("USERDEFINED")
379 && draft.object_type.is_none_or(|text| text.trim().is_empty())
380 {
381 return Err(ResourceError::SemanticViolation {
382 entity: None,
383 rule: "USERDEFINED_REQUIRES_OBJECT_TYPE",
384 });
385 }
386 }
387 if let Some(target) = draft.jurisdiction {
388 self.check_reference_select(
389 target,
390 "Jurisdiction",
391 "IfcActorSelect",
392 ACTOR_SELECT,
393 target,
394 )?;
395 }
396 for (attribute, target) in [
397 ("CurrentValue", draft.current_value),
398 ("OriginalValue", draft.original_value),
399 ] {
400 if let Some(target) = target {
401 self.check_reference(target, attribute, "IfcCostValue", target)?;
402 }
403 }
404 if responsible_persons.is_empty() {
408 return Err(ResourceError::InvalidDraft {
409 entity_type: ENTITY,
410 attribute: "ResponsiblePersons",
411 expected: "at least one person, per SET [1:?]",
412 });
413 }
414 for person in responsible_persons {
415 self.check_reference(*person, "ResponsiblePersons", "IfcPerson", *person)?;
416 }
417 let entity = build_entity(
418 self.schema,
419 ENTITY,
420 &[
421 ("GlobalId", Some(text(draft.global_id))),
422 ("Name", draft.name.map(text)),
423 ("Description", draft.description.map(text)),
424 ("ObjectType", draft.object_type.map(text)),
425 (
426 "PredefinedType",
427 draft.predefined_type.map(|t| Value::Enum(t.into())),
428 ),
429 ("Jurisdiction", draft.jurisdiction.map(Value::Ref)),
430 ("ResponsiblePersons", Some(refs(responsible_persons))),
431 ("LastUpdateDate", draft.last_update_date.map(text)),
432 ("CurrentValue", draft.current_value.map(Value::Ref)),
433 ("OriginalValue", draft.original_value.map(Value::Ref)),
434 ],
435 )?;
436 self.commit_create(entity)
437 }
438}
439
440#[derive(Debug, Clone, Copy, Default)]
442#[non_exhaustive]
443pub struct InventoryDraft<'a> {
444 pub global_id: &'a str,
446 pub name: Option<&'a str>,
448 pub description: Option<&'a str>,
450 pub object_type: Option<&'a str>,
452 pub predefined_type: Option<&'a str>,
454 pub jurisdiction: Option<EntityId>,
456 pub last_update_date: Option<&'a str>,
458 pub current_value: Option<EntityId>,
460 pub original_value: Option<EntityId>,
462}
463
464impl<'a> InventoryDraft<'a> {
465 #[must_use]
467 pub fn new(global_id: &'a str) -> Self {
468 Self {
469 global_id,
470 name: None,
471 description: None,
472 object_type: None,
473 predefined_type: None,
474 jurisdiction: None,
475 last_update_date: None,
476 current_value: None,
477 original_value: None,
478 }
479 }
480
481 #[must_use]
483 pub fn name(mut self, value: &'a str) -> Self {
484 self.name = Some(value);
485 self
486 }
487
488 #[must_use]
490 pub fn description(mut self, value: &'a str) -> Self {
491 self.description = Some(value);
492 self
493 }
494
495 #[must_use]
497 pub fn object_type(mut self, value: &'a str) -> Self {
498 self.object_type = Some(value);
499 self
500 }
501
502 #[must_use]
504 pub fn predefined_type(mut self, value: &'a str) -> Self {
505 self.predefined_type = Some(value);
506 self
507 }
508
509 #[must_use]
511 pub fn jurisdiction(mut self, value: EntityId) -> Self {
512 self.jurisdiction = Some(value);
513 self
514 }
515
516 #[must_use]
518 pub fn last_update_date(mut self, value: &'a str) -> Self {
519 self.last_update_date = Some(value);
520 self
521 }
522
523 #[must_use]
525 pub fn current_value(mut self, value: EntityId) -> Self {
526 self.current_value = Some(value);
527 self
528 }
529
530 #[must_use]
532 pub fn original_value(mut self, value: EntityId) -> Self {
533 self.original_value = Some(value);
534 self
535 }
536}