kiteconnect_async_wasm/models/portfolio/
conversions.rs

1use crate::models::common::{Exchange, Product, TransactionType};
2use serde::{Deserialize, Serialize};
3
4/// Portfolio conversion types
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "lowercase")]
7pub enum ConversionType {
8    /// Convert from CNC to MIS
9    Position,
10    /// Convert from MIS to CNC
11    Holding,
12}
13
14/// Portfolio conversion request
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ConversionRequest {
17    /// Exchange
18    pub exchange: Exchange,
19
20    /// Trading symbol
21    #[serde(rename = "tradingsymbol")]
22    pub trading_symbol: String,
23
24    /// Transaction type
25    #[serde(rename = "transaction_type")]
26    pub transaction_type: TransactionType,
27
28    /// Quantity to convert
29    pub quantity: u32,
30
31    /// From product type
32    #[serde(rename = "from_product")]
33    pub from_product: Product,
34
35    /// To product type
36    #[serde(rename = "to_product")]
37    pub to_product: Product,
38}
39
40/// Conversion response
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ConversionResponse {
43    /// Status of the conversion
44    pub status: String,
45
46    /// Message from the conversion operation
47    pub message: Option<String>,
48}
49
50/// Bulk conversion request for multiple instruments
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct BulkConversionRequest {
53    /// List of conversion requests
54    pub conversions: Vec<ConversionRequest>,
55}
56
57/// Bulk conversion response
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct BulkConversionResponse {
60    /// Status of the bulk conversion
61    pub status: String,
62
63    /// Individual conversion results
64    pub results: Vec<ConversionResult>,
65
66    /// Overall message
67    pub message: Option<String>,
68}
69
70/// Individual conversion result in bulk operation
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ConversionResult {
73    /// Trading symbol
74    #[serde(rename = "tradingsymbol")]
75    pub trading_symbol: String,
76
77    /// Exchange
78    pub exchange: Exchange,
79
80    /// Status of this specific conversion
81    pub status: String,
82
83    /// Message for this conversion
84    pub message: Option<String>,
85
86    /// Error details if conversion failed
87    pub error: Option<String>,
88}
89
90impl ConversionRequest {
91    /// Create a new conversion request
92    pub fn new(
93        exchange: Exchange,
94        trading_symbol: String,
95        transaction_type: TransactionType,
96        quantity: u32,
97        from_product: Product,
98        to_product: Product,
99    ) -> Self {
100        Self {
101            exchange,
102            trading_symbol,
103            transaction_type,
104            quantity,
105            from_product,
106            to_product,
107        }
108    }
109
110    /// Create a request to convert CNC to MIS (position conversion)
111    pub fn cnc_to_mis(
112        exchange: Exchange,
113        trading_symbol: String,
114        transaction_type: TransactionType,
115        quantity: u32,
116    ) -> Self {
117        Self::new(
118            exchange,
119            trading_symbol,
120            transaction_type,
121            quantity,
122            Product::CNC,
123            Product::MIS,
124        )
125    }
126
127    /// Create a request to convert MIS to CNC (holding conversion)
128    pub fn mis_to_cnc(
129        exchange: Exchange,
130        trading_symbol: String,
131        transaction_type: TransactionType,
132        quantity: u32,
133    ) -> Self {
134        Self::new(
135            exchange,
136            trading_symbol,
137            transaction_type,
138            quantity,
139            Product::MIS,
140            Product::CNC,
141        )
142    }
143
144    /// Create a request to convert NRML to MIS
145    pub fn nrml_to_mis(
146        exchange: Exchange,
147        trading_symbol: String,
148        transaction_type: TransactionType,
149        quantity: u32,
150    ) -> Self {
151        Self::new(
152            exchange,
153            trading_symbol,
154            transaction_type,
155            quantity,
156            Product::NRML,
157            Product::MIS,
158        )
159    }
160
161    /// Create a request to convert MIS to NRML
162    pub fn mis_to_nrml(
163        exchange: Exchange,
164        trading_symbol: String,
165        transaction_type: TransactionType,
166        quantity: u32,
167    ) -> Self {
168        Self::new(
169            exchange,
170            trading_symbol,
171            transaction_type,
172            quantity,
173            Product::MIS,
174            Product::NRML,
175        )
176    }
177
178    /// Get the conversion type based on products
179    pub fn conversion_type(&self) -> ConversionType {
180        match (&self.from_product, &self.to_product) {
181            (Product::CNC, Product::MIS) => ConversionType::Position,
182            (Product::MIS, Product::CNC) => ConversionType::Holding,
183            (Product::NRML, Product::MIS) => ConversionType::Position,
184            (Product::MIS, Product::NRML) => ConversionType::Position,
185            _ => ConversionType::Position, // Default to position conversion
186        }
187    }
188
189    /// Check if this is a valid conversion
190    pub fn is_valid_conversion(&self) -> bool {
191        // Define valid conversion pairs
192        matches!(
193            (&self.from_product, &self.to_product),
194            (Product::CNC, Product::MIS)
195                | (Product::MIS, Product::CNC)
196                | (Product::NRML, Product::MIS)
197                | (Product::MIS, Product::NRML)
198        )
199    }
200
201    /// Validate the conversion request
202    pub fn validate(&self) -> Result<(), String> {
203        if self.trading_symbol.is_empty() {
204            return Err("Trading symbol cannot be empty".to_string());
205        }
206
207        if self.quantity == 0 {
208            return Err("Quantity must be greater than 0".to_string());
209        }
210
211        if !self.is_valid_conversion() {
212            return Err(format!(
213                "Invalid conversion from {:?} to {:?}",
214                self.from_product, self.to_product
215            ));
216        }
217
218        Ok(())
219    }
220}
221
222impl BulkConversionRequest {
223    /// Create a new bulk conversion request
224    pub fn new() -> Self {
225        Self {
226            conversions: Vec::new(),
227        }
228    }
229
230    /// Add a conversion to the bulk request
231    pub fn add_conversion(mut self, conversion: ConversionRequest) -> Self {
232        self.conversions.push(conversion);
233        self
234    }
235
236    /// Add multiple conversions to the bulk request
237    pub fn add_conversions(mut self, conversions: Vec<ConversionRequest>) -> Self {
238        self.conversions.extend(conversions);
239        self
240    }
241
242    /// Validate all conversions in the bulk request
243    pub fn validate(&self) -> Result<(), Vec<String>> {
244        let mut errors = Vec::new();
245
246        if self.conversions.is_empty() {
247            errors.push("Bulk conversion request cannot be empty".to_string());
248        }
249
250        for (index, conversion) in self.conversions.iter().enumerate() {
251            if let Err(error) = conversion.validate() {
252                errors.push(format!("Conversion {}: {}", index + 1, error));
253            }
254        }
255
256        if errors.is_empty() {
257            Ok(())
258        } else {
259            Err(errors)
260        }
261    }
262
263    /// Get the total number of conversions
264    pub fn count(&self) -> usize {
265        self.conversions.len()
266    }
267
268    /// Check if the bulk request is empty
269    pub fn is_empty(&self) -> bool {
270        self.conversions.is_empty()
271    }
272}
273
274impl Default for BulkConversionRequest {
275    fn default() -> Self {
276        Self::new()
277    }
278}
279
280impl ConversionResponse {
281    /// Check if the conversion was successful
282    pub fn is_success(&self) -> bool {
283        self.status.to_lowercase() == "success"
284    }
285
286    /// Check if the conversion failed
287    pub fn is_failure(&self) -> bool {
288        !self.is_success()
289    }
290}
291
292impl BulkConversionResponse {
293    /// Check if all conversions were successful
294    pub fn is_all_success(&self) -> bool {
295        self.results.iter().all(|r| r.is_success())
296    }
297
298    /// Get the number of successful conversions
299    pub fn success_count(&self) -> usize {
300        self.results.iter().filter(|r| r.is_success()).count()
301    }
302
303    /// Get the number of failed conversions
304    pub fn failure_count(&self) -> usize {
305        self.results.iter().filter(|r| r.is_failure()).count()
306    }
307
308    /// Get successful conversion results
309    pub fn successful_conversions(&self) -> Vec<&ConversionResult> {
310        self.results.iter().filter(|r| r.is_success()).collect()
311    }
312
313    /// Get failed conversion results
314    pub fn failed_conversions(&self) -> Vec<&ConversionResult> {
315        self.results.iter().filter(|r| r.is_failure()).collect()
316    }
317}
318
319impl ConversionResult {
320    /// Check if this conversion was successful
321    pub fn is_success(&self) -> bool {
322        self.status.to_lowercase() == "success"
323    }
324
325    /// Check if this conversion failed
326    pub fn is_failure(&self) -> bool {
327        !self.is_success()
328    }
329
330    /// Get the error message if conversion failed
331    pub fn error_message(&self) -> Option<&str> {
332        self.error.as_deref().or(self.message.as_deref())
333    }
334}