Skip to main content

datasynth_ocpm/models/
object_type.rs

1//! Object type definitions for OCPM.
2//!
3//! Object types define the schema for business objects that participate
4//! in processes, including their lifecycle states and allowed relationships.
5
6use datasynth_core::models::BusinessProcess;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Definition of a business object type in OCPM.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ObjectType {
13    /// Unique identifier for the object type (e.g., "purchase_order")
14    pub type_id: String,
15    /// Human-readable name (e.g., "Purchase Order")
16    pub name: String,
17    /// Business process this type belongs to
18    pub business_process: BusinessProcess,
19    /// Lifecycle states for this object type
20    pub lifecycle_states: Vec<ObjectLifecycleState>,
21    /// Allowed relationships to other object types
22    pub relationships: Vec<ObjectRelationshipType>,
23    /// Activities that can occur on this object type
24    pub allowed_activities: Vec<String>,
25    /// Attributes schema (key -> type)
26    pub attributes: HashMap<String, AttributeType>,
27}
28
29impl ObjectType {
30    /// Create a Purchase Order object type for P2P.
31    pub fn purchase_order() -> Self {
32        Self {
33            type_id: "purchase_order".into(),
34            name: "Purchase Order".into(),
35            business_process: BusinessProcess::P2P,
36            lifecycle_states: vec![
37                ObjectLifecycleState::new("created", "Created", true, false),
38                ObjectLifecycleState::new("approved", "Approved", false, false),
39                ObjectLifecycleState::new("released", "Released", false, false),
40                ObjectLifecycleState::new("received", "Goods Received", false, false),
41                ObjectLifecycleState::new("invoiced", "Invoiced", false, false),
42                ObjectLifecycleState::new("paid", "Paid", false, true),
43                ObjectLifecycleState::new("cancelled", "Cancelled", false, true),
44            ],
45            relationships: vec![
46                ObjectRelationshipType::new(
47                    "contains",
48                    "Contains",
49                    "order_line",
50                    Cardinality::OneToMany,
51                    false,
52                ),
53                ObjectRelationshipType::new(
54                    "fulfilled_by",
55                    "Fulfilled By",
56                    "goods_receipt",
57                    Cardinality::OneToMany,
58                    false,
59                ),
60                ObjectRelationshipType::new(
61                    "invoiced_by",
62                    "Invoiced By",
63                    "vendor_invoice",
64                    Cardinality::OneToMany,
65                    false,
66                ),
67            ],
68            allowed_activities: vec![
69                "create_po".into(),
70                "approve_po".into(),
71                "release_po".into(),
72                "change_po".into(),
73                "cancel_po".into(),
74            ],
75            attributes: HashMap::from([
76                ("po_number".into(), AttributeType::String),
77                ("vendor_id".into(), AttributeType::String),
78                ("total_amount".into(), AttributeType::Decimal),
79                ("currency".into(), AttributeType::String),
80                ("created_date".into(), AttributeType::Date),
81            ]),
82        }
83    }
84
85    /// Create a Goods Receipt object type for P2P.
86    pub fn goods_receipt() -> Self {
87        Self {
88            type_id: "goods_receipt".into(),
89            name: "Goods Receipt".into(),
90            business_process: BusinessProcess::P2P,
91            lifecycle_states: vec![
92                ObjectLifecycleState::new("created", "Created", true, false),
93                ObjectLifecycleState::new("posted", "Posted", false, true),
94                ObjectLifecycleState::new("reversed", "Reversed", false, true),
95            ],
96            relationships: vec![
97                ObjectRelationshipType::new(
98                    "references",
99                    "References",
100                    "purchase_order",
101                    Cardinality::ManyToOne,
102                    true,
103                ),
104                ObjectRelationshipType::new(
105                    "contains",
106                    "Contains",
107                    "material",
108                    Cardinality::OneToMany,
109                    true,
110                ),
111            ],
112            allowed_activities: vec!["create_gr".into(), "post_gr".into(), "reverse_gr".into()],
113            attributes: HashMap::from([
114                ("gr_number".into(), AttributeType::String),
115                ("po_number".into(), AttributeType::String),
116                ("receipt_date".into(), AttributeType::Date),
117                ("quantity".into(), AttributeType::Decimal),
118            ]),
119        }
120    }
121
122    /// Create a Vendor Invoice object type for P2P.
123    pub fn vendor_invoice() -> Self {
124        Self {
125            type_id: "vendor_invoice".into(),
126            name: "Vendor Invoice".into(),
127            business_process: BusinessProcess::P2P,
128            lifecycle_states: vec![
129                ObjectLifecycleState::new("received", "Received", true, false),
130                ObjectLifecycleState::new("verified", "Verified", false, false),
131                ObjectLifecycleState::new("posted", "Posted", false, false),
132                ObjectLifecycleState::new("paid", "Paid", false, true),
133                ObjectLifecycleState::new("rejected", "Rejected", false, true),
134            ],
135            relationships: vec![
136                ObjectRelationshipType::new(
137                    "references_po",
138                    "References PO",
139                    "purchase_order",
140                    Cardinality::ManyToOne,
141                    false,
142                ),
143                ObjectRelationshipType::new(
144                    "references_gr",
145                    "References GR",
146                    "goods_receipt",
147                    Cardinality::ManyToMany,
148                    false,
149                ),
150            ],
151            allowed_activities: vec![
152                "receive_invoice".into(),
153                "verify_invoice".into(),
154                "post_invoice".into(),
155                "reject_invoice".into(),
156            ],
157            attributes: HashMap::from([
158                ("invoice_number".into(), AttributeType::String),
159                ("vendor_id".into(), AttributeType::String),
160                ("invoice_amount".into(), AttributeType::Decimal),
161                ("invoice_date".into(), AttributeType::Date),
162            ]),
163        }
164    }
165
166    /// Create a Sales Order object type for O2C.
167    pub fn sales_order() -> Self {
168        Self {
169            type_id: "sales_order".into(),
170            name: "Sales Order".into(),
171            business_process: BusinessProcess::O2C,
172            lifecycle_states: vec![
173                ObjectLifecycleState::new("created", "Created", true, false),
174                ObjectLifecycleState::new("credit_checked", "Credit Checked", false, false),
175                ObjectLifecycleState::new("released", "Released", false, false),
176                ObjectLifecycleState::new("delivered", "Delivered", false, false),
177                ObjectLifecycleState::new("invoiced", "Invoiced", false, false),
178                ObjectLifecycleState::new("paid", "Paid", false, true),
179                ObjectLifecycleState::new("cancelled", "Cancelled", false, true),
180            ],
181            relationships: vec![
182                ObjectRelationshipType::new(
183                    "contains",
184                    "Contains",
185                    "order_line",
186                    Cardinality::OneToMany,
187                    false,
188                ),
189                ObjectRelationshipType::new(
190                    "fulfilled_by",
191                    "Fulfilled By",
192                    "delivery",
193                    Cardinality::OneToMany,
194                    false,
195                ),
196            ],
197            allowed_activities: vec![
198                "create_so".into(),
199                "check_credit".into(),
200                "release_so".into(),
201                "change_so".into(),
202                "cancel_so".into(),
203            ],
204            attributes: HashMap::from([
205                ("so_number".into(), AttributeType::String),
206                ("customer_id".into(), AttributeType::String),
207                ("total_amount".into(), AttributeType::Decimal),
208                ("currency".into(), AttributeType::String),
209            ]),
210        }
211    }
212
213    /// Create a Delivery object type for O2C.
214    pub fn delivery() -> Self {
215        Self {
216            type_id: "delivery".into(),
217            name: "Delivery".into(),
218            business_process: BusinessProcess::O2C,
219            lifecycle_states: vec![
220                ObjectLifecycleState::new("created", "Created", true, false),
221                ObjectLifecycleState::new("picked", "Picked", false, false),
222                ObjectLifecycleState::new("packed", "Packed", false, false),
223                ObjectLifecycleState::new("shipped", "Shipped", false, true),
224                ObjectLifecycleState::new("cancelled", "Cancelled", false, true),
225            ],
226            relationships: vec![ObjectRelationshipType::new(
227                "fulfills",
228                "Fulfills",
229                "sales_order",
230                Cardinality::ManyToOne,
231                true,
232            )],
233            allowed_activities: vec![
234                "create_delivery".into(),
235                "pick".into(),
236                "pack".into(),
237                "ship".into(),
238            ],
239            attributes: HashMap::from([
240                ("delivery_number".into(), AttributeType::String),
241                ("so_number".into(), AttributeType::String),
242                ("ship_date".into(), AttributeType::Date),
243            ]),
244        }
245    }
246
247    /// Create a Customer Invoice object type for O2C.
248    pub fn customer_invoice() -> Self {
249        Self {
250            type_id: "customer_invoice".into(),
251            name: "Customer Invoice".into(),
252            business_process: BusinessProcess::O2C,
253            lifecycle_states: vec![
254                ObjectLifecycleState::new("created", "Created", true, false),
255                ObjectLifecycleState::new("posted", "Posted", false, false),
256                ObjectLifecycleState::new("sent", "Sent", false, false),
257                ObjectLifecycleState::new("paid", "Paid", false, true),
258                ObjectLifecycleState::new("written_off", "Written Off", false, true),
259            ],
260            relationships: vec![
261                ObjectRelationshipType::new(
262                    "references_so",
263                    "References SO",
264                    "sales_order",
265                    Cardinality::ManyToOne,
266                    false,
267                ),
268                ObjectRelationshipType::new(
269                    "references_delivery",
270                    "References Delivery",
271                    "delivery",
272                    Cardinality::ManyToMany,
273                    false,
274                ),
275            ],
276            allowed_activities: vec![
277                "create_invoice".into(),
278                "post_invoice".into(),
279                "send_invoice".into(),
280            ],
281            attributes: HashMap::from([
282                ("invoice_number".into(), AttributeType::String),
283                ("customer_id".into(), AttributeType::String),
284                ("invoice_amount".into(), AttributeType::Decimal),
285            ]),
286        }
287    }
288
289    /// Get all standard P2P object types.
290    pub fn p2p_types() -> Vec<Self> {
291        vec![
292            Self::purchase_order(),
293            Self::goods_receipt(),
294            Self::vendor_invoice(),
295        ]
296    }
297
298    /// Get all standard O2C object types.
299    pub fn o2c_types() -> Vec<Self> {
300        vec![
301            Self::sales_order(),
302            Self::delivery(),
303            Self::customer_invoice(),
304        ]
305    }
306}
307
308/// State in an object's lifecycle.
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct ObjectLifecycleState {
311    /// State identifier
312    pub state_id: String,
313    /// Human-readable name
314    pub name: String,
315    /// Is this an initial state (object starts here)
316    pub is_initial: bool,
317    /// Is this a terminal state (object ends here)
318    pub is_terminal: bool,
319    /// Valid transitions from this state
320    pub valid_transitions: Vec<String>,
321}
322
323impl ObjectLifecycleState {
324    /// Create a new lifecycle state.
325    pub fn new(state_id: &str, name: &str, is_initial: bool, is_terminal: bool) -> Self {
326        Self {
327            state_id: state_id.into(),
328            name: name.into(),
329            is_initial,
330            is_terminal,
331            valid_transitions: Vec::new(),
332        }
333    }
334
335    /// Add valid transitions from this state.
336    pub fn with_transitions(mut self, transitions: Vec<&str>) -> Self {
337        self.valid_transitions = transitions.into_iter().map(String::from).collect();
338        self
339    }
340}
341
342/// Type of relationship between object types.
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct ObjectRelationshipType {
345    /// Relationship type identifier
346    pub relationship_id: String,
347    /// Human-readable name
348    pub name: String,
349    /// Target object type ID
350    pub target_type_id: String,
351    /// Cardinality of the relationship
352    pub cardinality: Cardinality,
353    /// Is this relationship mandatory
354    pub is_mandatory: bool,
355}
356
357impl ObjectRelationshipType {
358    /// Create a new relationship type.
359    pub fn new(
360        relationship_id: &str,
361        name: &str,
362        target_type_id: &str,
363        cardinality: Cardinality,
364        is_mandatory: bool,
365    ) -> Self {
366        Self {
367            relationship_id: relationship_id.into(),
368            name: name.into(),
369            target_type_id: target_type_id.into(),
370            cardinality,
371            is_mandatory,
372        }
373    }
374}
375
376/// Cardinality of object relationships.
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
378#[serde(rename_all = "snake_case")]
379pub enum Cardinality {
380    /// One source to one target
381    OneToOne,
382    /// One source to many targets
383    OneToMany,
384    /// Many sources to one target
385    ManyToOne,
386    /// Many sources to many targets
387    ManyToMany,
388}
389
390/// Attribute types for object attributes.
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
392#[serde(rename_all = "snake_case")]
393pub enum AttributeType {
394    /// String value
395    String,
396    /// Integer value
397    Integer,
398    /// Decimal value (for monetary amounts)
399    Decimal,
400    /// Date value
401    Date,
402    /// DateTime value
403    DateTime,
404    /// Boolean value
405    Boolean,
406    /// Reference to another object type
407    Reference(String),
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    #[test]
415    fn test_purchase_order_type() {
416        let po_type = ObjectType::purchase_order();
417        assert_eq!(po_type.type_id, "purchase_order");
418        assert_eq!(po_type.business_process, BusinessProcess::P2P);
419        assert!(!po_type.lifecycle_states.is_empty());
420        assert!(!po_type.relationships.is_empty());
421    }
422
423    #[test]
424    fn test_p2p_types() {
425        let types = ObjectType::p2p_types();
426        assert_eq!(types.len(), 3);
427    }
428
429    #[test]
430    fn test_o2c_types() {
431        let types = ObjectType::o2c_types();
432        assert_eq!(types.len(), 3);
433    }
434}