Skip to main content

datasynth_core/models/sourcing/
bid.rs

1//! Supplier bid models for RFx responses.
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/// Status of a supplier bid.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
12#[serde(rename_all = "snake_case")]
13pub enum BidStatus {
14    /// Bid submitted, awaiting evaluation
15    #[default]
16    Submitted,
17    /// Under evaluation
18    UnderEvaluation,
19    /// Bid accepted (winner)
20    Accepted,
21    /// Bid rejected
22    Rejected,
23    /// Bid withdrawn by vendor
24    Withdrawn,
25    /// Technically disqualified
26    Disqualified,
27}
28
29/// Line item within a supplier bid.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct BidLineItem {
32    /// Item number (matches RFx line item)
33    pub item_number: u16,
34    /// Offered unit price
35    #[serde(with = "rust_decimal::serde::str")]
36    pub unit_price: Decimal,
37    /// Offered quantity
38    pub quantity: Decimal,
39    /// Total line amount
40    #[serde(with = "rust_decimal::serde::str")]
41    pub total_amount: Decimal,
42    /// Lead time in days
43    pub lead_time_days: u32,
44    /// Vendor's notes for this item
45    pub notes: Option<String>,
46}
47
48/// A supplier's bid in response to an RFx.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SupplierBid {
51    /// Unique bid identifier
52    pub bid_id: String,
53    /// RFx this bid responds to
54    pub rfx_id: String,
55    /// Vendor submitting the bid
56    pub vendor_id: String,
57    /// Company code
58    pub company_code: String,
59    /// Bid status
60    pub status: BidStatus,
61    /// Submission date
62    pub submission_date: NaiveDate,
63    /// Bid line items
64    pub line_items: Vec<BidLineItem>,
65    /// Total bid amount
66    #[serde(with = "rust_decimal::serde::str")]
67    pub total_amount: Decimal,
68    /// Validity period (days from submission)
69    pub validity_days: u32,
70    /// Payment terms offered
71    pub payment_terms: String,
72    /// Delivery terms (incoterms)
73    pub delivery_terms: Option<String>,
74    /// Technical proposal summary
75    pub technical_summary: Option<String>,
76    /// Whether the bid was submitted on time
77    pub is_on_time: bool,
78    /// Whether the bid is technically compliant
79    pub is_compliant: bool,
80}
81
82impl ToNodeProperties for SupplierBid {
83    fn node_type_name(&self) -> &'static str {
84        "supplier_bid"
85    }
86    fn node_type_code(&self) -> u16 {
87        322
88    }
89    fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
90        let mut p = HashMap::new();
91        p.insert(
92            "bidId".into(),
93            GraphPropertyValue::String(self.bid_id.clone()),
94        );
95        p.insert(
96            "rfxId".into(),
97            GraphPropertyValue::String(self.rfx_id.clone()),
98        );
99        p.insert(
100            "vendorId".into(),
101            GraphPropertyValue::String(self.vendor_id.clone()),
102        );
103        p.insert(
104            "entityCode".into(),
105            GraphPropertyValue::String(self.company_code.clone()),
106        );
107        p.insert(
108            "status".into(),
109            GraphPropertyValue::String(format!("{:?}", self.status)),
110        );
111        p.insert(
112            "submissionDate".into(),
113            GraphPropertyValue::Date(self.submission_date),
114        );
115        p.insert(
116            "bidAmount".into(),
117            GraphPropertyValue::Decimal(self.total_amount),
118        );
119        p.insert(
120            "validityDays".into(),
121            GraphPropertyValue::Int(self.validity_days as i64),
122        );
123        p.insert("isOnTime".into(), GraphPropertyValue::Bool(self.is_on_time));
124        p.insert(
125            "isCompliant".into(),
126            GraphPropertyValue::Bool(self.is_compliant),
127        );
128        p
129    }
130}