Skip to main content

datasynth_core/models/sourcing/
sourcing_project.rs

1//! Sourcing project models for strategic procurement initiatives.
2
3use chrono::NaiveDate;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8use super::super::graph_properties::{GraphPropertyValue, ToNodeProperties};
9
10/// Type of sourcing project.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
12#[serde(rename_all = "snake_case")]
13pub enum SourcingProjectType {
14    /// New category sourcing
15    #[default]
16    NewSourcing,
17    /// Contract renewal/renegotiation
18    Renewal,
19    /// Supplier consolidation
20    Consolidation,
21    /// Emergency sourcing
22    Emergency,
23    /// Strategic partnership
24    StrategicPartnership,
25}
26
27/// Status of a sourcing project.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum SourcingProjectStatus {
31    /// Project created, not yet started
32    #[default]
33    Draft,
34    /// Spend analysis in progress
35    SpendAnalysis,
36    /// Supplier qualification phase
37    Qualification,
38    /// RFx in progress
39    RfxActive,
40    /// Evaluating bids
41    Evaluation,
42    /// Contract negotiation
43    Negotiation,
44    /// Contract awarded
45    Awarded,
46    /// Project completed
47    Completed,
48    /// Project cancelled
49    Cancelled,
50}
51
52/// A sourcing project tracking a procurement initiative.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct SourcingProject {
55    /// Unique project identifier
56    pub project_id: String,
57    /// Project name
58    pub project_name: String,
59    /// Company code
60    pub company_code: String,
61    /// Project type
62    pub project_type: SourcingProjectType,
63    /// Current status
64    pub status: SourcingProjectStatus,
65    /// Spend category being sourced
66    pub category_id: String,
67    /// Estimated annual spend
68    #[serde(with = "rust_decimal::serde::str")]
69    pub estimated_annual_spend: Decimal,
70    /// Target savings percentage
71    pub target_savings_pct: f64,
72    /// Project owner (buyer/sourcing manager)
73    pub owner_id: String,
74    /// Start date
75    pub start_date: NaiveDate,
76    /// Target completion date
77    pub target_end_date: NaiveDate,
78    /// Actual completion date
79    pub actual_end_date: Option<NaiveDate>,
80    /// Related spend analysis ID
81    pub spend_analysis_id: Option<String>,
82    /// Related RFx event IDs
83    pub rfx_ids: Vec<String>,
84    /// Awarded contract ID
85    pub contract_id: Option<String>,
86    /// Actual savings achieved
87    pub actual_savings_pct: Option<f64>,
88}
89
90impl ToNodeProperties for SourcingProject {
91    fn node_type_name(&self) -> &'static str {
92        "sourcing_project"
93    }
94    fn node_type_code(&self) -> u16 {
95        320
96    }
97    fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
98        let mut p = HashMap::new();
99        p.insert(
100            "projectId".into(),
101            GraphPropertyValue::String(self.project_id.clone()),
102        );
103        p.insert(
104            "projectName".into(),
105            GraphPropertyValue::String(self.project_name.clone()),
106        );
107        p.insert(
108            "entityCode".into(),
109            GraphPropertyValue::String(self.company_code.clone()),
110        );
111        p.insert(
112            "projectType".into(),
113            GraphPropertyValue::String(format!("{:?}", self.project_type)),
114        );
115        p.insert(
116            "status".into(),
117            GraphPropertyValue::String(format!("{:?}", self.status)),
118        );
119        p.insert(
120            "estimatedValue".into(),
121            GraphPropertyValue::Decimal(self.estimated_annual_spend),
122        );
123        p.insert(
124            "targetSavingsPct".into(),
125            GraphPropertyValue::Float(self.target_savings_pct),
126        );
127        p.insert(
128            "owner".into(),
129            GraphPropertyValue::String(self.owner_id.clone()),
130        );
131        p.insert(
132            "startDate".into(),
133            GraphPropertyValue::Date(self.start_date),
134        );
135        p.insert(
136            "targetEndDate".into(),
137            GraphPropertyValue::Date(self.target_end_date),
138        );
139        p.insert(
140            "bidCount".into(),
141            GraphPropertyValue::Int(self.rfx_ids.len() as i64),
142        );
143        p.insert(
144            "isComplete".into(),
145            GraphPropertyValue::Bool(matches!(
146                self.status,
147                SourcingProjectStatus::Completed | SourcingProjectStatus::Awarded
148            )),
149        );
150        p
151    }
152}