datasynth_core/models/sourcing/
contract.rs1use chrono::NaiveDate;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8use super::super::graph_properties::{GraphPropertyValue, ToNodeProperties};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
12#[serde(rename_all = "snake_case")]
13pub enum ContractType {
14 #[default]
16 FixedPrice,
17 Blanket,
19 TimeAndMaterials,
21 CostPlus,
23 ServiceAgreement,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum ContractStatus {
31 #[default]
33 Draft,
34 PendingApproval,
36 Active,
38 Suspended,
40 Expired,
42 Terminated,
44 Renewed,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ContractTerms {
51 pub payment_terms: String,
53 pub delivery_terms: Option<String>,
55 pub warranty_months: Option<u32>,
57 pub early_termination_penalty_pct: Option<f64>,
59 pub auto_renewal: bool,
61 pub termination_notice_days: u32,
63 pub price_adjustment_clause: bool,
65 pub max_annual_price_increase_pct: Option<f64>,
67}
68
69impl Default for ContractTerms {
70 fn default() -> Self {
71 Self {
72 payment_terms: "NET30".to_string(),
73 delivery_terms: None,
74 warranty_months: None,
75 early_termination_penalty_pct: None,
76 auto_renewal: false,
77 termination_notice_days: 90,
78 price_adjustment_clause: false,
79 max_annual_price_increase_pct: None,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct ContractSla {
87 pub metric_name: String,
89 pub target_value: f64,
91 pub minimum_value: f64,
93 pub breach_penalty_pct: f64,
95 pub measurement_frequency: String,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ContractLineItem {
102 pub line_number: u16,
104 pub material_id: Option<String>,
106 pub description: String,
108 #[serde(with = "rust_decimal::serde::str")]
110 pub unit_price: Decimal,
111 pub uom: String,
113 pub min_quantity: Option<Decimal>,
115 pub max_quantity: Option<Decimal>,
117 #[serde(default)]
119 pub quantity_released: Decimal,
120 #[serde(default, with = "rust_decimal::serde::str")]
122 pub value_released: Decimal,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ProcurementContract {
128 pub contract_id: String,
130 pub company_code: String,
132 pub contract_type: ContractType,
134 pub status: ContractStatus,
136 pub vendor_id: String,
138 pub title: String,
140 pub sourcing_project_id: Option<String>,
142 pub bid_id: Option<String>,
144 pub start_date: NaiveDate,
146 pub end_date: NaiveDate,
148 #[serde(with = "rust_decimal::serde::str")]
150 pub total_value: Decimal,
151 #[serde(with = "rust_decimal::serde::str")]
153 pub consumed_value: Decimal,
154 pub terms: ContractTerms,
156 pub slas: Vec<ContractSla>,
158 pub line_items: Vec<ContractLineItem>,
160 pub category_id: String,
162 pub owner_id: String,
164 pub amendment_count: u32,
166 pub previous_contract_id: Option<String>,
168}
169
170impl ToNodeProperties for ProcurementContract {
171 fn node_type_name(&self) -> &'static str {
172 "procurement_contract"
173 }
174 fn node_type_code(&self) -> u16 {
175 324
176 }
177 fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
178 let mut p = HashMap::new();
179 p.insert(
180 "contractId".into(),
181 GraphPropertyValue::String(self.contract_id.clone()),
182 );
183 p.insert(
184 "entityCode".into(),
185 GraphPropertyValue::String(self.company_code.clone()),
186 );
187 p.insert(
188 "contractType".into(),
189 GraphPropertyValue::String(format!("{:?}", self.contract_type)),
190 );
191 p.insert(
192 "status".into(),
193 GraphPropertyValue::String(format!("{:?}", self.status)),
194 );
195 p.insert(
196 "vendorId".into(),
197 GraphPropertyValue::String(self.vendor_id.clone()),
198 );
199 p.insert(
200 "title".into(),
201 GraphPropertyValue::String(self.title.clone()),
202 );
203 p.insert(
204 "startDate".into(),
205 GraphPropertyValue::Date(self.start_date),
206 );
207 p.insert("endDate".into(), GraphPropertyValue::Date(self.end_date));
208 p.insert(
209 "totalValue".into(),
210 GraphPropertyValue::Decimal(self.total_value),
211 );
212 p.insert(
213 "consumedValue".into(),
214 GraphPropertyValue::Decimal(self.consumed_value),
215 );
216 p.insert(
217 "lineItemCount".into(),
218 GraphPropertyValue::Int(self.line_items.len() as i64),
219 );
220 p.insert(
221 "amendmentCount".into(),
222 GraphPropertyValue::Int(self.amendment_count as i64),
223 );
224 p.insert(
225 "isActive".into(),
226 GraphPropertyValue::Bool(matches!(self.status, ContractStatus::Active)),
227 );
228 p
229 }
230}