1use chrono::{DateTime, Utc};
8use rust_decimal::Decimal;
9use uuid::Uuid;
10
11use super::{CaseGenerationResult, OcpmEventGenerator, VariantType};
12use crate::models::{
13 ActivityType, EventObjectRef, ObjectAttributeValue, ObjectRelationship, ObjectType,
14};
15use datasynth_core::models::BusinessProcess;
16
17#[derive(Debug, Clone)]
19pub struct O2cDocuments {
20 pub so_number: String,
22 pub so_id: Uuid,
24 pub delivery_number: Option<String>,
26 pub delivery_id: Option<Uuid>,
28 pub invoice_number: Option<String>,
30 pub invoice_id: Option<Uuid>,
32 pub receipt_number: Option<String>,
34 pub receipt_id: Option<Uuid>,
36 pub customer_id: String,
38 pub company_code: String,
40 pub amount: Decimal,
42 pub currency: String,
44}
45
46impl O2cDocuments {
47 pub fn new(
49 so_number: &str,
50 customer_id: &str,
51 company_code: &str,
52 amount: Decimal,
53 currency: &str,
54 ) -> Self {
55 Self {
56 so_number: so_number.into(),
57 so_id: Uuid::new_v4(),
58 delivery_number: None,
59 delivery_id: None,
60 invoice_number: None,
61 invoice_id: None,
62 receipt_number: None,
63 receipt_id: None,
64 customer_id: customer_id.into(),
65 company_code: company_code.into(),
66 amount,
67 currency: currency.into(),
68 }
69 }
70
71 pub fn with_delivery(mut self, delivery_number: &str) -> Self {
73 self.delivery_number = Some(delivery_number.into());
74 self.delivery_id = Some(Uuid::new_v4());
75 self
76 }
77
78 pub fn with_invoice(mut self, invoice_number: &str) -> Self {
80 self.invoice_number = Some(invoice_number.into());
81 self.invoice_id = Some(Uuid::new_v4());
82 self
83 }
84
85 pub fn with_receipt(mut self, receipt_number: &str) -> Self {
87 self.receipt_number = Some(receipt_number.into());
88 self.receipt_id = Some(Uuid::new_v4());
89 self
90 }
91}
92
93impl OcpmEventGenerator {
94 pub fn generate_o2c_case(
96 &mut self,
97 documents: &O2cDocuments,
98 start_time: DateTime<Utc>,
99 available_users: &[String],
100 ) -> CaseGenerationResult {
101 let case_id = self.new_case_id();
102 let variant_type = self.select_variant_type();
103
104 let mut events = Vec::new();
105 let mut objects = Vec::new();
106 let mut relationships = Vec::new();
107 let mut current_time = start_time;
108
109 let so_type = ObjectType::sales_order();
111 let delivery_type = ObjectType::delivery();
112 let invoice_type = ObjectType::customer_invoice();
113
114 let so_object = self.create_object(
116 &so_type,
117 &documents.so_number,
118 &documents.company_code,
119 current_time,
120 );
121 objects.push(so_object.clone());
122
123 let create_so = ActivityType::create_so();
125 let resource = self.select_resource(&create_so, available_users);
126 let mut event = self.create_event(
127 &create_so,
128 current_time,
129 &resource,
130 &documents.company_code,
131 case_id,
132 );
133 event = event
134 .with_object(
135 EventObjectRef::created(so_object.object_id, &so_type.type_id)
136 .with_external_id(&documents.so_number),
137 )
138 .with_document_ref(&documents.so_number);
139 Self::add_event_attribute(
140 &mut event,
141 "amount",
142 ObjectAttributeValue::Decimal(documents.amount),
143 );
144 Self::add_event_attribute(
145 &mut event,
146 "customer_id",
147 ObjectAttributeValue::String(documents.customer_id.clone()),
148 );
149 events.push(event);
150
151 current_time = self.calculate_event_time(current_time, &create_so);
153
154 let check_credit = ActivityType::check_credit();
155 let resource = self.select_resource(&check_credit, available_users);
156 let mut event = self.create_event(
157 &check_credit,
158 current_time,
159 &resource,
160 &documents.company_code,
161 case_id,
162 );
163 event = event.with_object(
164 EventObjectRef::updated(so_object.object_id, &so_type.type_id)
165 .with_external_id(&documents.so_number),
166 );
167 Self::add_event_attribute(
168 &mut event,
169 "credit_result",
170 ObjectAttributeValue::String("approved".into()),
171 );
172 events.push(event);
173
174 current_time = self.calculate_event_time(current_time, &check_credit);
176
177 let release_so = ActivityType::release_so();
178 let resource = self.select_resource(&release_so, available_users);
179 let mut event = self.create_event(
180 &release_so,
181 current_time,
182 &resource,
183 &documents.company_code,
184 case_id,
185 );
186 event = event.with_object(
187 EventObjectRef::updated(so_object.object_id, &so_type.type_id)
188 .with_external_id(&documents.so_number),
189 );
190 events.push(event);
191
192 if matches!(variant_type, VariantType::ErrorPath) {
194 let case_trace = self.create_case_trace(
195 case_id,
196 &events,
197 BusinessProcess::O2C,
198 so_object.object_id,
199 &so_type.type_id,
200 &documents.company_code,
201 );
202 return CaseGenerationResult {
203 events,
204 objects,
205 relationships,
206 case_trace,
207 variant_type,
208 };
209 }
210
211 if let Some(delivery_number) = &documents.delivery_number {
213 current_time = self.calculate_event_time(current_time, &release_so);
214 current_time += self.generate_inter_activity_delay(60, 480);
215
216 let delivery_object = self.create_object(
217 &delivery_type,
218 delivery_number,
219 &documents.company_code,
220 current_time,
221 );
222 objects.push(delivery_object.clone());
223
224 relationships.push(ObjectRelationship::new(
226 "references",
227 delivery_object.object_id,
228 &delivery_type.type_id,
229 so_object.object_id,
230 &so_type.type_id,
231 ));
232
233 let create_delivery = ActivityType::create_delivery();
234 let resource = self.select_resource(&create_delivery, available_users);
235 let mut event = self.create_event(
236 &create_delivery,
237 current_time,
238 &resource,
239 &documents.company_code,
240 case_id,
241 );
242 event = event
243 .with_object(
244 EventObjectRef::created(delivery_object.object_id, &delivery_type.type_id)
245 .with_external_id(delivery_number),
246 )
247 .with_document_ref(delivery_number);
248 events.push(event);
249
250 current_time = self.calculate_event_time(current_time, &create_delivery);
252
253 let pick = ActivityType::pick();
254 let resource = self.select_resource(&pick, available_users);
255 let mut event = self.create_event(
256 &pick,
257 current_time,
258 &resource,
259 &documents.company_code,
260 case_id,
261 );
262 event = event.with_object(
263 EventObjectRef::updated(delivery_object.object_id, &delivery_type.type_id)
264 .with_external_id(delivery_number),
265 );
266 events.push(event);
267
268 current_time = self.calculate_event_time(current_time, &pick);
270
271 let pack = ActivityType::pack();
272 let resource = self.select_resource(&pack, available_users);
273 let mut event = self.create_event(
274 &pack,
275 current_time,
276 &resource,
277 &documents.company_code,
278 case_id,
279 );
280 event = event.with_object(
281 EventObjectRef::updated(delivery_object.object_id, &delivery_type.type_id)
282 .with_external_id(delivery_number),
283 );
284 events.push(event);
285
286 current_time = self.calculate_event_time(current_time, &pack);
288
289 let ship = ActivityType::ship();
290 let resource = self.select_resource(&ship, available_users);
291 let mut event = self.create_event(
292 &ship,
293 current_time,
294 &resource,
295 &documents.company_code,
296 case_id,
297 );
298 event = event
299 .with_object(
300 EventObjectRef::updated(delivery_object.object_id, &delivery_type.type_id)
301 .with_external_id(delivery_number),
302 )
303 .with_object(
304 EventObjectRef::updated(so_object.object_id, &so_type.type_id)
305 .with_external_id(&documents.so_number),
306 );
307 events.push(event);
308 }
309
310 if let Some(invoice_number) = &documents.invoice_number {
312 current_time = self.calculate_event_time(current_time, &ActivityType::ship());
313 current_time += self.generate_inter_activity_delay(60, 1440);
314
315 let invoice_object = self.create_object(
316 &invoice_type,
317 invoice_number,
318 &documents.company_code,
319 current_time,
320 );
321 objects.push(invoice_object.clone());
322
323 relationships.push(ObjectRelationship::new(
325 "references",
326 invoice_object.object_id,
327 &invoice_type.type_id,
328 so_object.object_id,
329 &so_type.type_id,
330 ));
331
332 let create_invoice = ActivityType::create_customer_invoice();
333 let resource = self.select_resource(&create_invoice, available_users);
334 let mut event = self.create_event(
335 &create_invoice,
336 current_time,
337 &resource,
338 &documents.company_code,
339 case_id,
340 );
341 event = event
342 .with_object(
343 EventObjectRef::created(invoice_object.object_id, &invoice_type.type_id)
344 .with_external_id(invoice_number),
345 )
346 .with_object(
347 EventObjectRef::updated(so_object.object_id, &so_type.type_id)
348 .with_external_id(&documents.so_number),
349 )
350 .with_document_ref(invoice_number);
351 Self::add_event_attribute(
352 &mut event,
353 "invoice_amount",
354 ObjectAttributeValue::Decimal(documents.amount),
355 );
356 events.push(event);
357
358 current_time = self.calculate_event_time(current_time, &create_invoice);
360
361 let post_invoice = ActivityType::post_customer_invoice();
362 let resource = self.select_resource(&post_invoice, available_users);
363 let mut event = self.create_event(
364 &post_invoice,
365 current_time,
366 &resource,
367 &documents.company_code,
368 case_id,
369 );
370 event = event.with_object(
371 EventObjectRef::updated(invoice_object.object_id, &invoice_type.type_id)
372 .with_external_id(invoice_number),
373 );
374 events.push(event);
375
376 if documents.receipt_number.is_some() {
378 current_time = self.calculate_event_time(current_time, &post_invoice);
379 current_time += self.generate_inter_activity_delay(1440, 43200); let receive_payment = ActivityType::receive_payment();
382 let resource = self.select_resource(&receive_payment, available_users);
383 let mut event = self.create_event(
384 &receive_payment,
385 current_time,
386 &resource,
387 &documents.company_code,
388 case_id,
389 );
390 event = event
391 .with_object(
392 EventObjectRef::consumed(invoice_object.object_id, &invoice_type.type_id)
393 .with_external_id(invoice_number),
394 )
395 .with_object(
396 EventObjectRef::consumed(so_object.object_id, &so_type.type_id)
397 .with_external_id(&documents.so_number),
398 )
399 .with_document_ref(documents.receipt_number.as_deref().unwrap_or(""));
400 Self::add_event_attribute(
401 &mut event,
402 "payment_amount",
403 ObjectAttributeValue::Decimal(documents.amount),
404 );
405 events.push(event);
406 }
407 }
408
409 let case_trace = self.create_case_trace(
410 case_id,
411 &events,
412 BusinessProcess::O2C,
413 so_object.object_id,
414 &so_type.type_id,
415 &documents.company_code,
416 );
417
418 CaseGenerationResult {
419 events,
420 objects,
421 relationships,
422 case_trace,
423 variant_type,
424 }
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_o2c_case_generation() {
434 let mut generator = OcpmEventGenerator::new(42);
435 let documents = O2cDocuments::new(
436 "SO-000001",
437 "C000001",
438 "1000",
439 Decimal::new(15000, 0),
440 "USD",
441 )
442 .with_delivery("DEL-000001")
443 .with_invoice("INV-000001")
444 .with_receipt("REC-000001");
445
446 let result = generator.generate_o2c_case(
447 &documents,
448 Utc::now(),
449 &["user001".into(), "user002".into()],
450 );
451
452 assert!(result.events.len() >= 3);
454 assert!(!result.objects.is_empty());
456 assert!(!result.case_trace.activity_sequence.is_empty());
458 }
459}