From eb41e727ac4db111bf4f241555732590d5bf7e41 Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Thu, 30 Nov 2023 15:05:25 +1100 Subject: [PATCH 01/10] some fields pub, description() fn --- src/tmf648/quote.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/tmf648/quote.rs b/src/tmf648/quote.rs index e64ea252..4c62b6cf 100644 --- a/src/tmf648/quote.rs +++ b/src/tmf648/quote.rs @@ -33,14 +33,23 @@ pub enum QuoteStateType { #[derive(Clone, Default, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Quote { - id: String, - href: String, + /// Unique Id + pub id: String, + /// HTML Reference to quote + pub href: String, + #[serde(skip_serializing_if = "Option::is_none")] description: Option, + /// External reference + #[serde(skip_serializing_if = "Option::is_none")] external_id: Option, + /// Notes for Quote + #[serde(skip_serializing_if = "Option::is_none")] note: Option>, - state: QuoteStateType, + /// Quote status + pub state: QuoteStateType, quote_item: Vec, - version: String, + /// Current quote version + pub version: String, } impl Quote { @@ -70,6 +79,16 @@ impl Quote { self.quote_item.push(item); Ok(String::from("Quote Item Added")) } + + /// Get a description for this quote + pub fn description(&self) -> String { + match &self.description { + Some(d) => d.clone(), + None => { + format!("Quote-{}",self.id) + } + } + } } #[cfg(test)] From 0606be78a660c3048b24d722ad372edf3613040d Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Thu, 30 Nov 2023 15:27:10 +1100 Subject: [PATCH 02/10] tests for description --- src/tmf648/quote.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/tmf648/quote.rs b/src/tmf648/quote.rs index 4c62b6cf..e53a6fa7 100644 --- a/src/tmf648/quote.rs +++ b/src/tmf648/quote.rs @@ -37,8 +37,9 @@ pub struct Quote { pub id: String, /// HTML Reference to quote pub href: String, + /// Quote description #[serde(skip_serializing_if = "Option::is_none")] - description: Option, + pub description: Option, /// External reference #[serde(skip_serializing_if = "Option::is_none")] external_id: Option, @@ -89,6 +90,7 @@ impl Quote { } } } + } #[cfg(test)] @@ -110,4 +112,19 @@ mod test { assert_eq!(quote.state, QuoteStateType::Accepted); } + + #[test] + fn quote_test_description() { + let mut quote = Quote::new(); + quote.description = Some("description".to_string()); + + assert_eq!(quote.description(), "description".to_string()) + } + + #[test] + fn quote_test_no_description() { + let quote = Quote::new(); + + assert_eq!(quote.description(),format!("Quote-{}",quote.id)); + } } From 4099e63ec3c866de72aefe289a01d78c9527428e Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Fri, 1 Dec 2023 14:22:19 +1100 Subject: [PATCH 03/10] remove None --- src/tmf674/geographic_site.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tmf674/geographic_site.rs b/src/tmf674/geographic_site.rs index e8827139..19c4032f 100644 --- a/src/tmf674/geographic_site.rs +++ b/src/tmf674/geographic_site.rs @@ -38,11 +38,14 @@ impl From for PlaceRefOrValue { #[serde(rename_all = "camelCase")] pub struct GeographicSite { /// Id + #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, /// HREF + #[serde(skip_serializing_if = "Option::is_none")] pub href: Option, /// Name pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] place: Option, } From 5eb83a92b1f94af13d7853526ab6d2cc5002e745 Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Fri, 1 Dec 2023 14:22:29 +1100 Subject: [PATCH 04/10] restrict classes --- src/common/event.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/event.rs b/src/common/event.rs index 026d9648..da301c00 100644 --- a/src/common/event.rs +++ b/src/common/event.rs @@ -1,6 +1,8 @@ //! Asynchronous Events //! use serde::{Deserialize, Serialize}; +use crate::HasId; +use std::fmt::Display; /// Generic Event structure, will be linked into event specific payloads. #[derive(Clone, Default, Debug, Deserialize, Serialize)] @@ -44,7 +46,7 @@ pub struct Event { } /// Trait for types that can generate an event -pub trait EventPayload { +pub trait EventPayload { /// Generate a new event payload fn generate_event(&self,event_type : U) -> Event; } \ No newline at end of file From 988a20fa316a8c7365cc912de20590ca7f86958a Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Fri, 1 Dec 2023 14:22:38 +1100 Subject: [PATCH 05/10] Remove None values --- src/tmf622/product_order.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tmf622/product_order.rs b/src/tmf622/product_order.rs index ed2a4aaf..aed7343d 100644 --- a/src/tmf622/product_order.rs +++ b/src/tmf622/product_order.rs @@ -19,8 +19,11 @@ const PO_PATH: &str = "order"; #[derive(Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProductOrder { + #[serde(skip_serializing_if = "Option::is_none")] id: Option, + #[serde(skip_serializing_if = "Option::is_none")] href: Option, + #[serde(skip_serializing_if = "Option::is_none")] order_date: Option, product_order_item: Vec, related_party: Vec, From a09559223d4ef5763d5c0065f01df1b5e5a74119 Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Mon, 4 Dec 2023 16:09:25 +1100 Subject: [PATCH 06/10] fix access to quote fields --- Cargo.toml | 2 +- src/tmf648/quote.rs | 70 +++++++++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0ecf7638..c224d4bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tmflib" -version = "0.1.3" +version = "0.1.5" edition = "2021" authors = ["Ryan Ruckley "] description = "Interface library for processing TMF payloads" diff --git a/src/tmf648/quote.rs b/src/tmf648/quote.rs index e53a6fa7..396729c2 100644 --- a/src/tmf648/quote.rs +++ b/src/tmf648/quote.rs @@ -1,11 +1,10 @@ //! Quote Module use serde::{Deserialize, Serialize}; -use uuid::Uuid; use super::quote_item::QuoteItem; use super::MOD_PATH; use crate::common::note::Note; -use crate::LIB_PATH; +use crate::{LIB_PATH, HasId, CreateTMF}; const QUOTE_PATH: &str = "quote"; const QUOTE_VERS: &str = "1.0"; @@ -34,9 +33,11 @@ pub enum QuoteStateType { #[serde(rename_all = "camelCase")] pub struct Quote { /// Unique Id - pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, /// HTML Reference to quote - pub href: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub href: Option, /// Quote description #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, @@ -47,27 +48,24 @@ pub struct Quote { #[serde(skip_serializing_if = "Option::is_none")] note: Option>, /// Quote status - pub state: QuoteStateType, - quote_item: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub state: Option, + /// Vector of quote items + #[serde(skip_serializing_if = "Option::is_none")] + pub quote_item: Option>, /// Current quote version - pub version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } impl Quote { /// Create a new Product Quote pub fn new() -> Quote { - let id = Uuid::new_v4().to_string(); - let href = format!("/{}/{}/{}/{}", LIB_PATH, MOD_PATH, QUOTE_PATH, id); - Quote { - id, - href, - description: None, - external_id: None, - note: None, - version: QUOTE_VERS.to_string(), - state: QuoteStateType::Accepted, - quote_item: vec![], - } + let mut quote = Quote::create(); + quote.version = Some(QUOTE_VERS.to_string()); + quote.state = Some(QuoteStateType::Accepted); + quote.quote_item = Some(vec![]); + quote } /// Set external Id for this quote @@ -77,7 +75,7 @@ impl Quote { /// Add a quote item into a product quote pub fn add_quote(&mut self, item: QuoteItem) -> Result { - self.quote_item.push(item); + self.quote_item.as_mut().unwrap().push(item); Ok(String::from("Quote Item Added")) } @@ -86,31 +84,55 @@ impl Quote { match &self.description { Some(d) => d.clone(), None => { - format!("Quote-{}",self.id) + format!("Quote-{}",self.get_id()) } } } } +impl HasId for Quote { + fn generate_href(&mut self) { + let href = format!("/{}/{}/{}/{}",LIB_PATH,MOD_PATH,QUOTE_PATH,self.get_id()); + self.href = Some(href); + } + fn generate_id(&mut self) { + let id = Quote::get_uuid(); + self.id = Some(id); + self.generate_href(); + } + fn get_class() -> String { + QUOTE_PATH.to_owned() + } + fn get_href(&self) -> String { + self.href.as_ref().unwrap().clone() + } + fn get_id(&self) -> String { + self.id.as_ref().unwrap().clone() + } +} + +impl CreateTMF for Quote {} + #[cfg(test)] mod test { use super::QuoteStateType; use crate::tmf648::quote::QUOTE_VERS; + use crate::HasId; use super::Quote; #[test] fn quote_test_new_vers() { let quote = Quote::new(); - assert_eq!(quote.version, QUOTE_VERS.to_string()); + assert_eq!(quote.version, Some(QUOTE_VERS.to_string())); } #[test] fn quote_test_new_state() { let quote = Quote::new(); - assert_eq!(quote.state, QuoteStateType::Accepted); + assert_eq!(quote.state, Some(QuoteStateType::Accepted)); } #[test] @@ -125,6 +147,6 @@ mod test { fn quote_test_no_description() { let quote = Quote::new(); - assert_eq!(quote.description(),format!("Quote-{}",quote.id)); + assert_eq!(quote.description(),format!("Quote-{}",quote.get_id())); } } From f502862ff16419c32d675a9ebd14592d4f060f61 Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Tue, 5 Dec 2023 12:22:18 +1100 Subject: [PATCH 07/10] missing fields --- src/tmf648/quote.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tmf648/quote.rs b/src/tmf648/quote.rs index 396729c2..c3f74d10 100644 --- a/src/tmf648/quote.rs +++ b/src/tmf648/quote.rs @@ -56,6 +56,12 @@ pub struct Quote { /// Current quote version #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, + /// Order Submission Date + #[serde(skip_serializing_if = "Option::is_none")] + pub order_date: Option, + /// Requested Start Date + #[serde(skip_serializing_if = "Option::is_none")] + pub start_date: Option, } impl Quote { From dccd71bc942b6f0cb0e5eca318b0e15c84b2b3d9 Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Tue, 5 Dec 2023 12:22:48 +1100 Subject: [PATCH 08/10] make fields public Add missing product info --- src/tmf648/quote_item.rs | 90 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/src/tmf648/quote_item.rs b/src/tmf648/quote_item.rs index 2d9581f8..a56ca928 100644 --- a/src/tmf648/quote_item.rs +++ b/src/tmf648/quote_item.rs @@ -4,18 +4,92 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::common::attachment::AttachmentRefOrValue; +use crate::common::note::Note; +use crate::common::related_party::RelatedParty; +use crate::tmf620::product_specification::ProductSpecificationRef; + +/// Status of product for Quote Item +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub enum ProductStatusType { + /// Created + #[default] + Created, + /// Wait for Active + PendingActive, + /// Cancelled + Cancelled, + /// Active + Active, + /// Wait for terminate + PendingTerminate, + /// Terminated + Terminated, + /// Suspended + Suspended, + /// Aborted + Aborted, +} + +// Not sure if this should be housed in TMF620 but sample payload shows it being local to QuoteItem +/// Quote Item Product +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct ProductRefOrValue { + /// Unique Id + #[serde(skip_serializing_if = "Option::is_none")] + pub id : Option, + /// HTTP Reference + #[serde(skip_serializing_if = "Option::is_none")] + pub href : Option, + /// Product Description (from TMF620) + #[serde(skip_serializing_if = "Option::is_none")] + pub description : Option, + /// Is this a bundle (from TMF620) + #[serde(skip_serializing_if = "Option::is_none")] + pub is_bundle: Option, + /// Is this customer visible (from TMF620) + #[serde(skip_serializing_if = "Option::is_none")] + pub is_customer_visible : Option, + /// Product Name + pub name : String, + /// Product serial number (if known) + #[serde(skip_serializing_if = "Option::is_none")] + pub product_serial_number : Option, + /// Status of product + #[serde(skip_serializing_if = "Option::is_none")] + pub status : Option, + /// Product Specification (TMF620) + #[serde(skip_serializing_if = "Option::is_none")] + pub product_specification : Option, +} + + /// Quote Item, line item for a product quote #[derive(Clone, Default, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct QuoteItem { - id: String, + /// Unique Id + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] action: Option, - quantity: u16, + /// Quantity + pub quantity: u16, + /// Child Quote Items + #[serde(skip_serializing_if = "Option::is_none")] + pub quote_item: Option>, + /// Attachments + #[serde(skip_serializing_if = "Option::is_none")] + pub attachment: Option>, + /// Notes + #[serde(skip_serializing_if = "Option::is_none")] + pub note : Option>, + /// Related Party #[serde(skip_serializing_if = "Option::is_none")] - quote_item: Option>, + pub related_party : Option>, + /// Product #[serde(skip_serializing_if = "Option::is_none")] - attachment: Option>, + pub product : Option, } impl QuoteItem { @@ -23,11 +97,9 @@ impl QuoteItem { pub fn new() -> QuoteItem { let id = Uuid::new_v4().to_string(); QuoteItem { - id, - action: None, - quantity: 1, - quote_item: None, - attachment: None, + id : Some(id), + quantity : 1, + ..Default::default() } } } From 7cdbca220ae10e37ee3cba04e0fb51bd0ee4c904 Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Wed, 13 Dec 2023 16:33:44 +1100 Subject: [PATCH 09/10] Add Price,QuotePrice to Quote --- examples/create_quote.rs | 14 ++++- src/tmf648/mod.rs | 1 + src/tmf648/quote.rs | 10 ++++ src/tmf648/quote_item.rs | 11 ++++ src/tmf648/quote_price.rs | 112 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/tmf648/quote_price.rs diff --git a/examples/create_quote.rs b/examples/create_quote.rs index f6c4ed02..7ffe3fe6 100644 --- a/examples/create_quote.rs +++ b/examples/create_quote.rs @@ -1,12 +1,20 @@ //! Create Quote Example -use tmflib::tmf648::{quote::Quote, quote_item::QuoteItem}; +use tmflib::tmf648::{quote::Quote, quote_item::QuoteItem, quote_price::{Price,QuotePrice}}; fn main() { // Create a quote - let item = QuoteItem::new(); + let mut item = QuoteItem::new(); + let price = Price::new_ex(100.0); + let quote_price = QuotePrice::new("Subscription").price(price).period("Monthly"); + item.price(quote_price); let mut quote = Quote::new(); let _result = quote.add_quote(item); - let _result = quote.with_external_id(String::from("ExternalId")); + let _result = quote.with_external_id(String::from("EXT123")); + + let total_price = Price::new_ex(3600.0); + + let quote_total_price = QuotePrice::new("Total Contract").price(total_price).period("Contract"); + quote.price(quote_total_price); dbg!("e); } \ No newline at end of file diff --git a/src/tmf648/mod.rs b/src/tmf648/mod.rs index 8dac3ee1..787bde95 100644 --- a/src/tmf648/mod.rs +++ b/src/tmf648/mod.rs @@ -18,3 +18,4 @@ const MOD_PATH: &str = "tmf648/v4"; pub mod quote; pub mod quote_item; +pub mod quote_price; diff --git a/src/tmf648/quote.rs b/src/tmf648/quote.rs index c3f74d10..95d6b808 100644 --- a/src/tmf648/quote.rs +++ b/src/tmf648/quote.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use super::quote_item::QuoteItem; use super::MOD_PATH; +use super::quote_price::QuotePrice; use crate::common::note::Note; use crate::{LIB_PATH, HasId, CreateTMF}; @@ -62,6 +63,9 @@ pub struct Quote { /// Requested Start Date #[serde(skip_serializing_if = "Option::is_none")] pub start_date: Option, + /// Total Quote Pricing + #[serde(skip_serializing_if = "Option::is_none")] + pub quote_total_price : Option>, } impl Quote { @@ -71,6 +75,7 @@ impl Quote { quote.version = Some(QUOTE_VERS.to_string()); quote.state = Some(QuoteStateType::Accepted); quote.quote_item = Some(vec![]); + quote.quote_total_price = Some(vec![]); quote } @@ -85,6 +90,11 @@ impl Quote { Ok(String::from("Quote Item Added")) } + /// Add a price entry to this quote + pub fn price(&mut self, price : QuotePrice) { + self.quote_total_price.as_mut().unwrap().push(price); + } + /// Get a description for this quote pub fn description(&self) -> String { match &self.description { diff --git a/src/tmf648/quote_item.rs b/src/tmf648/quote_item.rs index a56ca928..c06cc6e2 100644 --- a/src/tmf648/quote_item.rs +++ b/src/tmf648/quote_item.rs @@ -8,6 +8,8 @@ use crate::common::note::Note; use crate::common::related_party::RelatedParty; use crate::tmf620::product_specification::ProductSpecificationRef; +use super::quote_price::QuotePrice; + /// Status of product for Quote Item #[derive(Clone, Default, Debug, Deserialize, Serialize)] pub enum ProductStatusType { @@ -90,6 +92,9 @@ pub struct QuoteItem { /// Product #[serde(skip_serializing_if = "Option::is_none")] pub product : Option, + /// Quote Item Pricing + #[serde(skip_serializing_if = "Option::is_none")] + quote_item_price : Option>, } impl QuoteItem { @@ -99,7 +104,13 @@ impl QuoteItem { QuoteItem { id : Some(id), quantity : 1, + quote_item_price : Some(vec![]), ..Default::default() } } + + /// Add QuotePrice to this QuoteItem + pub fn price(&mut self, price : QuotePrice) { + self.quote_item_price.as_mut().unwrap().push(price); + } } diff --git a/src/tmf648/quote_price.rs b/src/tmf648/quote_price.rs new file mode 100644 index 00000000..2c5de3cc --- /dev/null +++ b/src/tmf648/quote_price.rs @@ -0,0 +1,112 @@ +//! Quote Price Struct +//! + +use serde::{Deserialize,Serialize}; + +use crate::tmf620::product_offering_price::ProductOfferingPriceRef; + +/// Default tax rate for Australian market. +const AUS_TAX_RATE : f32 = 0.10; + +/// Price Structure +#[derive(Copy,Clone, Default, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Price { + percentage : f32, + tax_rate: f32, + duty_free_amount : f32, + tax_included_amount : f32, +} + +impl Price { + /// Create a new Price object using a tax inclusive price + pub fn new_inc(inc_price : f32) -> Price { + let mut price = Price { + tax_rate : AUS_TAX_RATE, + ..Default::default() + }; + price.set_inc_price(inc_price); + price + } + + /// Create a new Price object using a tax exclusive price + pub fn new_ex(ex_price : f32) -> Price { + let mut price = Price { + tax_rate : AUS_TAX_RATE, + ..Default::default() + }; + price.set_ex_price(ex_price); + price + } + + /// Set the tax inclusive price + pub fn set_inc_price(&mut self, inc_price : f32) { + self.tax_included_amount = inc_price; + self.duty_free_amount = inc_price / self.tax_rate; + } + + /// Set the tax exclusive price + pub fn set_ex_price(&mut self, ex_price : f32) { + self.duty_free_amount = ex_price; + self.tax_included_amount = ex_price * (1.0+self.tax_rate); + } +} + +/// Quote Price Structure +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct QuotePrice { + /// Description + #[serde(skip_serializing_if = "Option::is_none")] + pub description : Option, + #[serde(skip_serializing_if = "Option::is_none")] + /// Name of price entry + pub name : Option, + #[serde(skip_serializing_if = "Option::is_none")] + price_type : Option, + #[serde(skip_serializing_if = "Option::is_none")] + recurring_charge_period: Option, + #[serde(skip_serializing_if = "Option::is_none")] + unit_of_measure : Option, + #[serde(skip_serializing_if = "Option::is_none")] + /// Pricing information + pub price : Option, + #[serde(skip_serializing_if = "Option::is_none")] + product_offering_price : Option, +} + +impl QuotePrice { + /// Create a new QuotePrice object with a given name + pub fn new(name : &str) -> QuotePrice { + QuotePrice { + name : Some(name.to_owned()), + ..Default::default() + } + } + /// Return the price inclusive of Tax + pub fn inc_tax(&self) -> f32 { + match self.price { + Some(p) => p.tax_included_amount, + None => 0.0, + } + } + /// Return the price exclusive of Tax + pub fn ex_tax(&self) -> f32 { + match self.price { + Some(p) => p.duty_free_amount, + None => 0.0, + } + } + + /// Add pricing to this QuotePrice + pub fn price(mut self, price : Price) -> QuotePrice { + self.price = Some(price); + self + } + + /// Set the period + pub fn period(mut self, period : &str) -> QuotePrice { + self.recurring_charge_period = Some(period.to_owned()); + self + } +} \ No newline at end of file From 61a0e2c4eb5342175d4f0324a8fd108787a1b76c Mon Sep 17 00:00:00 2001 From: Ryan Ruckley Date: Wed, 13 Dec 2023 16:42:10 +1100 Subject: [PATCH 10/10] Some comments. --- examples/create_quote.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/create_quote.rs b/examples/create_quote.rs index 7ffe3fe6..50089c83 100644 --- a/examples/create_quote.rs +++ b/examples/create_quote.rs @@ -2,18 +2,29 @@ use tmflib::tmf648::{quote::Quote, quote_item::QuoteItem, quote_price::{Price,QuotePrice}}; fn main() { - // Create a quote + // Create a quote using various components + + // First create a quote item let mut item = QuoteItem::new(); + // Create a price for this item let price = Price::new_ex(100.0); + // Add price to QuotePrice and set period let quote_price = QuotePrice::new("Subscription").price(price).period("Monthly"); + // add QuotePrice to item item.price(quote_price); + // Create the new Quote let mut quote = Quote::new(); + // Add the item to the quote let _result = quote.add_quote(item); + // Set the external Id let _result = quote.with_external_id(String::from("EXT123")); + // Create a total price for the quote let total_price = Price::new_ex(3600.0); + // Create QuotePrice object for the total price and set period let quote_total_price = QuotePrice::new("Total Contract").price(total_price).period("Contract"); + // Add QuotePrice to quote quote.price(quote_total_price); dbg!("e);