use std::{collections::HashMap, fs::File, io}; use chrono::{DateTime, NaiveDateTime}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; use serde_json::Value; pub trait CommonRecipeTrait { fn insert_comment(&mut self, comment: String); fn compare(&self, another_setting: &Self) -> Vec; } macro_rules! compare_field { ($self: ident, $another_setting: ident, $list: ident, $field: ident) => { if $self.$field != $another_setting.$field { $list.push(stringify!($field).to_string()); } }; } macro_rules! update_each_field { ($self: ident, $another_setting: ident,$field:ident) => { $self.$field = $another_setting.$field; }; } /// The above code defines a Rust struct called Recipe with various fields. /// /// Properties: /// /// * `Timestamp`: The Timestamp property is a string that represents the timestamp of the recipe. It is /// used to indicate when the recipe was created or last modified. /// * `MachineSetting`: MachineSetting is a struct that contains information about the settings of the /// machine used for the recipe. It may include properties such as temperature, pressure, speed, etc. /// * `Recipe01`: Recipe01 is a vector of Recipe01 structs. /// * `MaterialSetting`: MaterialSetting is a vector of MaterialSetting structs. Each MaterialSetting /// struct represents a specific material setting for the recipe. /// * `Topping`: The `Topping` property is a struct that contains information about the toppings used in /// the recipe. It is not defined in the code snippet you provided, so I cannot provide further details /// about its structure. /// * `MaterialCode`: MaterialCode is a vector (array) that contains instances of the MaterialCode /// struct. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Recipe { pub Timestamp: String, pub MachineSetting: MachineSetting, pub Recipe01: Vec, pub MaterialSetting: Vec, pub Topping: Topping, pub MaterialCode: Vec, #[serde(flatten)] pub extra: std::collections::HashMap, } impl Recipe { /// The `init` function initializes a `Recipe` struct by deserializing a JSON string, and if there /// is an error, it prints the error message and returns a default `Recipe` struct. /// /// Arguments: /// /// * `s`: The parameter `s` is a `String` that represents a JSON string. It is used to initialize /// the `Recipe` struct. /// /// Returns: /// /// The function `init` returns an instance of the `Recipe` struct. pub fn init(s: String) -> Self { let recipe = serde_json::from_str(&s); match recipe { Ok(r) => r, Err(e) => { println!("{:?}", e); Recipe { Timestamp: todo!(), MachineSetting: todo!(), Recipe01: todo!(), MaterialSetting: todo!(), Topping: todo!(), MaterialCode: todo!(), extra: todo!(), } } } } /// Get undefined fields from the recipe. This may included newer fields. /// pub fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; } Some(serde_json::to_value(self.extra.clone()).unwrap()) } /// The function `search_pd` searches for a recipe with a matching product code and returns it as an /// option. /// /// Arguments: /// /// * `product_code`: The `product_code` parameter is a `String` that represents the code of the /// product we want to search for in the `Recipe01` collection. /// /// Returns: /// /// an `Option` type, specifically `Option<&Recipe01>`. pub fn search_pd(&self, product_code: String) -> Option<&Recipe01> { self.Recipe01.iter().find(|&r| { if r.SubMenu.is_none() { r.productCode == product_code } else { r.SubMenu .as_ref() .unwrap() .iter() .filter(|x| x.productCode == product_code) .count() > 0 || r.productCode == product_code } }) } /// Similar to `search_pd` but ignoring exacted matching. pub fn search_pd_by_no_country_code(&self, product_code: String) -> Option<&Recipe01> { self.Recipe01.iter().find(|&r| { if r.SubMenu.is_none() { r.productCode.contains(&product_code) } else { r.SubMenu .as_ref() .unwrap() .iter() .filter(|x| x.productCode.contains(&product_code)) .count() > 0 || r.productCode.contains(&product_code) } }) } /// Mulitple product code searching with parallel and no exact matching. pub fn search_multi_in_parallel(&self, pds: Vec) -> Vec> { let mut found = Vec::new(); pds.par_iter() .map(|pd| { self.Recipe01 .par_iter() .find_any(|x| { if x.SubMenu.is_none() { x.productCode.contains(pd) } else { x.SubMenu .clone() .unwrap() .iter() .filter(|x2| x2.productCode.contains(pd)) .count() > 0 || x.productCode.contains(pd) } }) .cloned() }) .collect_into_vec(&mut found); found } /// Get position of product code in the recipe's `Recipe01` pub fn get_pd_index(&self, product_code: String) -> usize { self.Recipe01 .iter() .position(|r| { if r.SubMenu.is_none() { r.productCode == product_code } else { r.SubMenu .clone() .unwrap() .iter() .filter(|x2| x2.productCode == product_code) .count() > 0 || r.productCode == product_code } }) .unwrap() } /// Search expected material setting if existed pub fn search_material_settings(&self, material_code: String) -> Option<&MaterialSetting> { self.MaterialSetting .iter() .find(|r| r.id.to_string() == material_code) } /// Search expected topping list if existed pub fn search_topping_list(&self, id: String) -> Option<&ToppingList> { self.Topping.ToppingList.iter().find(|&r| r.id == id) } /// Search expected topping group if existed pub fn search_topping_group(&self, id: String) -> Option<&ToppingGroup> { self.Topping.ToppingGroup.iter().find(|&r| r.groupID == id) } /// Search expected material code if existed pub fn search_material_code(&self, material_code: String) -> Option<&MaterialCode> { self.MaterialCode .iter() .find(|&r| r.materialID == material_code) } /// Check if expected product code is different in both recipes. /// This may return `false` if either one did not have this product code. pub fn diff_pd_between_recipes(&self, another_recipe: &Recipe, product_code: String) -> bool { let menu1 = Self::search_pd(self, product_code.clone()); let menu2 = Self::search_pd(another_recipe, product_code.clone()); if menu1.is_none() || menu2.is_none() { return false; } let menu1 = menu1.unwrap(); let menu2 = menu2.unwrap(); menu1 == menu2 } /// Similar to `diff_pd_between_recipes` but will not check exacted matching. pub fn diff_pd_between_recipes_ignore_country( &self, another_recipe: &Recipe, product_code: String, ) -> bool { let menu1 = Self::search_pd_by_no_country_code(self, product_code.clone()); let menu2 = Self::search_pd_by_no_country_code(another_recipe, product_code.clone()); if menu1.is_none() || menu2.is_none() { return false; } let menu1 = menu1.unwrap(); let menu2 = menu2.unwrap(); menu1 == menu2 } /// Similar to `diff_pd_between_recipes` but will check each field of /// recipe and list them out if any. pub fn deep_diff_pd_between_recipes( &self, another_recipe: &Recipe, product_code: String, ) -> Vec<(String, bool)> { let mut result = Vec::new(); let self_recipe = Self::search_pd_by_no_country_code(self, product_code.clone()); let another_recipe = Self::search_pd_by_no_country_code(another_recipe, product_code.clone()); if self_recipe.is_some() && another_recipe.is_some() { let sr = self_recipe.unwrap(); let ar = another_recipe.unwrap(); let diff_field_list = sr.compare(ar); for key in sr.to_map().keys() { if diff_field_list.contains(key) || key.contains(".") { result.push((key.to_string(), true)); } else { result.push((key.to_string(), false)); } } } result } pub fn diff_material_setting_between_recipes( &self, another_recipe: &Recipe, material_code: String, ) -> bool { let menu1 = Self::search_material_settings(self, material_code.clone()); let menu2 = Self::search_material_settings(another_recipe, material_code.clone()); if menu1.is_none() || menu2.is_none() { return false; } let menu1 = menu1.unwrap(); let menu2 = menu2.unwrap(); menu1 == menu2 } pub fn mat_setting_exist_in_updated(&self, another_recipe: &Recipe) -> Vec { let mut result = Vec::new(); for ms in another_recipe.MaterialSetting.clone() { // println!("ms => {}", ms.id.to_string()); let mat_master = Self::search_material_settings(self, ms.id.to_string()); let mat_updated = Self::search_material_settings(another_recipe, ms.id.to_string()); // do check extra // println!("mat_master => {:?}", mat_master); // println!("mat_updated => {:?}", mat_updated); if mat_master.is_none() && mat_updated.is_some() { result.push(ms.id.to_string()); } } result } // for getting new menu // pub fn list_diff_pd_between_recipes(&self, another_recipe: &Recipe) -> Vec { // let mut list = Vec::new(); // for r in &self.Recipe01 { // if !another_recipe.diff_pd_between_recipes(another_recipe, r.productCode.clone()) { // list.push(r.productCode.clone()); // } // } // list // } pub fn list_diff_material_settings(&self, another_recipe: &Recipe) -> Vec { let mut list = Vec::new(); for r in &self.MaterialSetting { if !another_recipe .diff_material_setting_between_recipes(another_recipe, r.id.to_string()) { list.push(r.id.clone()); } } list } /// The `update_menu` function updates a recipe in the menu with the information from another /// recipe, based on the product code. /// /// Arguments: /// /// * `another_recipe`: `another_recipe` is a reference to a `Recipe` object. /// * `product_code`: The `product_code` parameter is a `String` that represents the code of the /// product to be updated in the menu. pub fn update_menu( &mut self, another_recipe: &Recipe, product_code: String, field: Option, ) { let updated = another_recipe.search_pd(product_code.clone()); let updated_clone = updated.unwrap().clone(); let master_idx = self .Recipe01 .iter() .position(|r| r.productCode == product_code); if let Some(idx) = master_idx { if let Some(att) = field { match att.as_str() { "recipes" => { self.Recipe01[idx].recipes = updated_clone.recipes; } "SubMenu" => { // do search by product code // let self_submenu = self.Recipe01[idx].SubMenu.clone(); // loop submenu if self_submenu.is_some() { let self_submenu = self_submenu.unwrap(); for updated_sub_idx in 0..updated_clone.clone().SubMenu.unwrap().len() { // search position in master let master_sub_idx = self_submenu.iter().position(|r| { r.productCode == updated_clone.clone().SubMenu.unwrap()[updated_sub_idx] .productCode }); if let Some(master_sub_idx) = master_sub_idx { self.Recipe01[idx].SubMenu.as_mut().unwrap()[master_sub_idx] = updated_clone.clone().SubMenu.unwrap()[updated_sub_idx] .clone(); } } } } "ToppingSet" => { self.Recipe01[idx].ToppingSet = updated_clone.ToppingSet; } _ => {} } } else { // make another check if self.Recipe01[idx].productCode == updated_clone.productCode { self.Recipe01[idx] = updated_clone; } } } } /// The function `update_material_settings` updates the material settings of a recipe with the /// material settings from another recipe, based on a given material code. /// /// Arguments: /// /// * `another_recipe`: A reference to a Recipe object that contains the updated material settings. /// * `material_code`: The `material_code` parameter is a `String` that represents the code of a /// material. pub fn update_material_settings(&mut self, another_recipe: &Recipe, material_code: String) { let updated = another_recipe.search_material_settings(material_code.clone()); let updated_clone = updated.unwrap().clone(); let master_idx = self .MaterialSetting .iter() .position(|r| r.id == material_code); if let Some(idx) = master_idx { // make another check if self.MaterialSetting[idx].id == updated_clone.id { self.MaterialSetting[idx] = updated_clone; } } } /// The function exports a Rust struct to a JSON file. pub fn export_to_json_file(self, outpath: Option) { let json = serde_json::to_string(&self).unwrap(); let json2: Value = serde_json::from_str(&json).unwrap(); if let Some(outpath) = outpath { let writer = File::create(outpath).unwrap(); let _ = serde_json::to_writer_pretty(writer, &json2); } else { println!("Default save to (execute)/recipe.json"); let writer = File::create("recipe.json").unwrap(); let _ = serde_json::to_writer_pretty(writer, &json2); } } pub fn get_additional_field_of_attr( &self, field_name: &str, index: Option, ) -> Option { match field_name { "MaterialSetting" => Some(self.MachineSetting.get_additional_fields().into()), "Recipe01" => Some( self.Recipe01 .get(index.unwrap() as usize) .unwrap() .get_additional_fields() .into(), ), "MaterialCode" => Some( self.MaterialCode .get(index.unwrap() as usize) .unwrap() .get_additional_fields() .into(), ), "ToppingList" => Some( self.Topping .ToppingList .get(index.unwrap() as usize) .unwrap() .get_additional_fields() .into(), ), "ToppingGroup" => Some( self.Topping .ToppingGroup .get(index.unwrap() as usize) .unwrap() .get_additional_fields() .into(), ), "SubMenu" => Some( self.Recipe01 .get(index.unwrap() as usize) .unwrap() .SubMenu .clone() .unwrap() .iter() .map(|r| r.get_additional_fields()) .collect::(), ), _ => None, } } pub fn set_topping_group_id_not_null(&mut self, product_code: &str) { // get address of productCode let idx = self.get_pd_index(product_code.to_string()); for t in 0..self.Recipe01[idx].ToppingSet.clone().unwrap().len() { let curr_top = &self.Recipe01[idx].ToppingSet.clone().unwrap()[t]; // println!("{} curr_top => {:?}", product_code.clone(), curr_top); if curr_top.clone().ListGroupID.is_some() && curr_top.groupID.is_none() { self.Recipe01[idx].ToppingSet.as_mut().unwrap()[t].groupID = Some(Value::String( curr_top .clone() .ListGroupID .unwrap() .get(0) .unwrap() .to_string(), )); } } } pub fn list_all_menu_with_this_param(&mut self, param: &str) -> Vec { let mut result = Vec::new(); for r in &self.Recipe01 { // if r.productCode.contains(param) { // result.push(r.productCode.clone()); // } for rpl in r.recipes.clone() { if let Some(StringParam) = rpl.StringParam { if StringParam.as_str().unwrap().contains(param) { result.push( r.productCode.clone() + "." + rpl.materialPathId.as_str().unwrap_or_default() + ":" + StringParam.as_str().unwrap(), ); } } } } println!( "list_all_menu_with_this_param :: result => {:?}", result.len() ); result } pub fn list_all_menu_with_this_material(&mut self, material: Value) -> Vec { let mut result = Vec::new(); for r in &self.Recipe01 { for rpl in r.recipes.clone() { // println!("rpl => {:?}", rpl); if rpl.materialPathId == material { result.push(r.productCode.clone()); } } } println!( "list_all_menu_with_this_material :: result => {:?}", result.len() ); result } pub fn list_extra_by_field(&mut self, field_name: &str) -> Vec { let mut res_map = Vec::new(); for r in &self.Recipe01 { if r.extra.contains_key(field_name) { // res_map.insert(r.productCode.clone(), field_name); res_map.push(r.productCode.clone()); } if let Some(submenu) = r.SubMenu.clone() { for s in submenu { if s.extra.contains_key(field_name) { // res_map.insert(s.productCode.clone(), field_name); res_map.push(s.productCode.clone()) } } } } println!("list_extra_by_field::Total = {}", res_map.len()); res_map } // zone import recipe // // pub fn add_prefix_country_code(&mut self) {} } #[allow(non_snake_case)] fn StrShowTextErrorDefault() -> Option> { Some(vec![ Value::String("เต่าบินเกิดเหตุขัดข้อง".into()), Value::String("Shoot! This is un expected...".into()), Value::String("test".into()), Value::Null, Value::Null, Value::Null, Value::Null, Value::Null, ]) } #[derive(Debug, Default, Serialize)] pub struct PartialRecipe { pub recipes: Vec, pub material_settings: Vec, } impl PartialRecipe { pub fn sync_new(&mut self, new_recipe: Vec, new_material: Vec) { self.recipes.extend(new_recipe); self.material_settings.extend(new_material); } pub fn export(self, output: &str) { let json = serde_json::to_string(&self).unwrap(); let json2: Value = serde_json::from_str(&json).unwrap(); let writer = File::create(output).unwrap(); let _ = serde_json::to_writer_pretty(writer, &json2); } } /// The above code defines a Rust struct called MachineSetting with several fields. /// /// Properties: /// /// * `Comment`: A vector of strings that represents comments about the machine setting. /// * `RecipeTag`: A string that represents the tag of the recipe for the machine. /// * `StrTextShowError`: StrTextShowError is an optional field that can contain a vector of values. /// * `configNumber`: The `configNumber` property is of type `Value`. It represents a configuration /// number for the machine setting. /// * `temperatureMax`: The `temperatureMax` property is of type `Value`. It represents the maximum /// temperature setting for a machine. /// * `temperatureMin`: The `temperatureMin` property is of type `Value`. It represents the minimum /// temperature setting for the machine. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MachineSetting { pub Comment: Option>, pub RecipeTag: String, #[serde(default = "StrShowTextErrorDefault")] pub StrTextShowError: Option>, pub configNumber: Value, pub temperatureMax: Value, pub temperatureMin: Value, #[serde(flatten)] pub extra: std::collections::HashMap, } pub trait MachineSettingTrait { fn get_comment(&self) -> Vec; fn get_recipe_tag(&self) -> String; fn get_str_text_show_error(&self) -> Option>; fn get_config_number(&self) -> Value; fn get_temperature_max(&self) -> Value; fn get_temperature_min(&self) -> Value; } impl MachineSetting { pub fn create_empty() -> Self { Self { Comment: None, RecipeTag: String::new(), StrTextShowError: None, configNumber: Value::from(0), temperatureMax: Value::from(0), temperatureMin: Value::from(0), extra: HashMap::new(), } } } impl MachineSettingTrait for MachineSetting { fn get_comment(&self) -> Vec { if let Some(comment) = &self.Comment { return comment.to_owned(); } Vec::new() } fn get_recipe_tag(&self) -> String { self.RecipeTag.clone() } fn get_str_text_show_error(&self) -> Option> { self.StrTextShowError.clone() } fn get_config_number(&self) -> Value { self.configNumber.clone() } fn get_temperature_max(&self) -> Value { self.temperatureMax.clone() } fn get_temperature_min(&self) -> Value { self.temperatureMin.clone() } } impl CommonRecipeTrait for MachineSetting { fn insert_comment(&mut self, comment: String) { // do get length let len = self.Comment.clone().unwrap().len(); self.Comment.as_mut().unwrap().insert(len, comment.clone()); // self.get_comment().push(comment); // self.Comment.push(comment); } fn compare(&self, another_setting: &MachineSetting) -> Vec { let mut list = Vec::new(); // compare_field!(self, another_setting, list, Comment); compare_field!(self, another_setting, list, RecipeTag); compare_field!(self, another_setting, list, configNumber); compare_field!(self, another_setting, list, temperatureMax); compare_field!(self, another_setting, list, temperatureMin); list } } #[allow(non_snake_case)] fn BlankString() -> Option { Some("".to_string()) } #[allow(non_snake_case)] fn BlankBool() -> Option { Some(false) } #[allow(non_snake_case)] fn BlankVecString() -> Option> { Some(vec![].into()) } #[allow(non_snake_case)] fn BlankVecRecipe() -> Option> { Some(vec![].into()) } #[allow(non_snake_case)] fn BlankVecMenuTopping() -> Option> { Some(vec![].into()) } #[allow(non_snake_case)] fn BlankOtherStrShowTextError() -> Option> { Some(vec![ "".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string(), ]) } #[allow(non_snake_case)] fn BlankValueString() -> Option { Some(Value::String("".into())) } #[allow(non_snake_case, dead_code)] fn BlankValueArray() -> Option { Some(Value::Array(vec![].into())) } #[allow(non_snake_case, dead_code)] fn BlankValueBool() -> Option { Some(Value::Bool(false.into())) } /// The above code defines a Rust struct named Recipe01 with various fields of different types. /// /// Properties: /// /// * `Description`: An optional string that describes the recipe. /// * `ExtendID`: The ExtendID property is of type Value. It is used to store additional identification /// information for the recipe. /// * `OnTOP`: The `OnTOP` property is of type `Value`. It represents the value associated with the /// "OnTOP" field in the `Recipe01` struct. /// * `LastChange`: LastChange is an optional field that represents the last time the recipe was /// changed. It is of type Value, which can hold various types of values such as strings, numbers, /// booleans, etc. /// * `MenuStatus`: The MenuStatus property is of type Value. It represents the status of the menu item. /// * `StringParam`: An optional value that can hold any type of data. /// * `TextForWarningBeforePay`: An optional vector of strings that represents the text for warning /// before payment. /// * `cashPrice`: The `cashPrice` property is of type `Value` and represents the price of the recipe in /// cash. /// * `changerecipe`: The `changerecipe` property is an optional string that represents the change /// recipe for a particular recipe. It is used to indicate any changes or modifications made to the /// original recipe. /// * `disable`: A boolean value indicating whether the recipe is disabled or not. /// * `disable_by_cup`: A boolean value indicating whether the recipe is disabled based on the cup size. /// * `disable_by_ice`: A boolean flag indicating whether the recipe is disabled by ice. /// * `EncoderCount`: The `EncoderCount` property is of type `Value` and represents the count of /// encoders. It is used to keep track of the number of encoders associated with the recipe. /// * `id`: The `id` property is of type `Value` and represents the unique identifier of the recipe. /// * `isUse`: A boolean value indicating whether the recipe is in use or not. /// * `isShow`: A boolean value indicating whether the recipe should be shown or not. /// * `name`: The name of the recipe. /// * `nonCashPrice`: The `nonCashPrice` property is of type `Value` and represents the non-cash price /// of the recipe. /// * `otherDescription`: The `otherDescription` property is an optional field that represents an /// additional description for the recipe. /// * `otherName`: The `otherName` property is an optional field that represents an alternative name for /// the recipe. It is of type `Option`, which means it can either be `Some(String)` if a value /// is present, or `None` if no value is provided. /// * `productCode`: A unique code that identifies the recipe. /// * `recipes`: A vector of RecipeList structs. /// * `SubMenu`: SubMenu is an optional vector of Recipe01 structs. It represents a sub-menu of recipes /// that can be accessed within the main recipe. Each sub-menu item is of type Recipe01. /// * `ToppingSet`: ToppingSet is a vector of MenuToppingList structs. It represents the set of toppings /// available for the recipe. /// * `total_time`: The `total_time` property represents the total time required to prepare the recipe. /// * `total_weight`: The property "total_weight" is of type "Value". It represents the total weight of /// the recipe. /// * `uriData`: An optional string field that represents the URI data associated with the recipe. /// * `useGram`: A boolean value indicating whether the recipe uses grams for measurement or not. /// * `weight_float`: The `weight_float` property is of type `Value` and represents the weight of the /// recipe as a floating-point number. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Recipe01 { #[serde(default = "BlankString")] pub Description: Option, pub ExtendID: Value, pub OnTOP: Value, #[serde(default = "BlankValueString")] pub LastChange: Option, pub MenuStatus: Value, #[serde(default = "BlankValueString")] pub StringParam: Option, #[serde(default = "BlankVecString")] pub TextForWarningBeforePay: Option>, pub cashPrice: Value, #[serde(default = "BlankString")] pub changerecipe: Option, // pub disable: bool, // pub disable_by_cup: bool, // pub disable_by_ice: bool, pub EncoderCount: Value, pub id: Value, pub isUse: bool, // pub isShow: bool, #[serde(default = "BlankString")] pub name: Option, pub nonCashPrice: Value, #[serde(default = "BlankString")] pub otherDescription: Option, #[serde(default = "BlankString")] pub otherName: Option, pub productCode: String, pub recipes: Vec, #[serde(default = "BlankVecRecipe")] pub SubMenu: Option>, #[serde(default = "BlankVecMenuTopping")] pub ToppingSet: Option>, pub total_time: Value, pub total_weight: Value, #[serde(default = "BlankString")] pub uriData: Option, pub useGram: bool, pub weight_float: Value, #[serde(flatten)] pub extra: std::collections::HashMap, } pub trait Recipe01Trait { fn insert_sub_menu(&mut self, sub_menu: Recipe01); fn compare(&self, another_recipe01: &Recipe01) -> Vec; fn default() -> Recipe01; fn is_replacable(self, another_recipe01: Recipe01, is_manual: bool) -> bool; fn update_menu_by_field(&mut self, another_recipe01: Recipe01, field: String) -> Recipe01; fn get_additional_fields(&self) -> Option; } impl Recipe01Trait for Recipe01 { fn insert_sub_menu(&mut self, sub_menu: Recipe01) { self.SubMenu = Some(vec![sub_menu]); } fn compare(&self, another_recipe01: &Recipe01) -> Vec { let mut list = Vec::new(); compare_field!(self, another_recipe01, list, Description); compare_field!(self, another_recipe01, list, ExtendID); compare_field!(self, another_recipe01, list, OnTOP); compare_field!(self, another_recipe01, list, LastChange); compare_field!(self, another_recipe01, list, MenuStatus); compare_field!(self, another_recipe01, list, StringParam); compare_field!(self, another_recipe01, list, TextForWarningBeforePay); compare_field!(self, another_recipe01, list, cashPrice); compare_field!(self, another_recipe01, list, changerecipe); // compare_field!(self, another_recipe01, list, disable); // compare_field!(self, another_recipe01, list, disable_by_cup); // compare_field!(self, another_recipe01, list, disable_by_ice); compare_field!(self, another_recipe01, list, EncoderCount); compare_field!(self, another_recipe01, list, id); compare_field!(self, another_recipe01, list, isUse); // compare_field!(self, another_recipe01, list, isShow); compare_field!(self, another_recipe01, list, name); compare_field!(self, another_recipe01, list, nonCashPrice); compare_field!(self, another_recipe01, list, otherDescription); compare_field!(self, another_recipe01, list, otherName); compare_field!(self, another_recipe01, list, productCode); if self.recipes.len().eq(&another_recipe01.recipes.len()) { for ri in 0..self.recipes.len() { let repl_diff = self .recipes .get(ri) .unwrap() .compare(another_recipe01.recipes.get(ri).unwrap()); let mut current_diff = repl_diff .iter() .map(|x| format!("recipes.{ri}.{x}").to_string()) .collect::>(); if current_diff.len() > 0 { list.append(&mut current_diff); } } } else { list.push("recipes".to_string()); // diff by size } compare_field!(self, another_recipe01, list, SubMenu); compare_field!(self, another_recipe01, list, ToppingSet); compare_field!(self, another_recipe01, list, total_time); compare_field!(self, another_recipe01, list, total_weight); compare_field!(self, another_recipe01, list, uriData); compare_field!(self, another_recipe01, list, useGram); compare_field!(self, another_recipe01, list, weight_float); // if self.Description != another_recipe01.Description { // list.push("Description".to_string()); // } list } /// default Recipe01: require for null fn default() -> Recipe01 { Recipe01 { Description: todo!(), ExtendID: todo!(), OnTOP: todo!(), LastChange: todo!(), MenuStatus: todo!(), StringParam: todo!(), TextForWarningBeforePay: todo!(), cashPrice: todo!(), changerecipe: todo!(), // disable: todo!(), // disable_by_cup: todo!(), // disable_by_ice: todo!(), EncoderCount: todo!(), id: todo!(), isUse: todo!(), // isShow: todo!(), name: todo!(), nonCashPrice: todo!(), otherDescription: todo!(), otherName: todo!(), productCode: todo!(), recipes: todo!(), SubMenu: todo!(), ToppingSet: todo!(), total_time: todo!(), total_weight: todo!(), uriData: todo!(), useGram: todo!(), weight_float: todo!(), extra: todo!(), } } /// The function `is_replacable` checks if two recipes can be replaced based on their `LastChange` /// values and user input. /// /// Arguments: /// /// * `another_recipe01`: The `another_recipe01` parameter is of type `Recipe01`. /// * `is_manual`: A boolean value indicating whether the function should prompt the user for input /// or not. /// /// Returns: /// /// a boolean value. fn is_replacable(self, another_recipe01: Recipe01, is_manual: bool) -> bool { let master_value_exist = self.LastChange.is_some(); let dev_value_exist = another_recipe01.LastChange.is_some(); let mut user_input = String::new(); if !master_value_exist && !dev_value_exist { if is_manual { println!( "{} {}: this recipe has no LastChange, return `FALSE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return false; } else if user_input.trim() == "n" { return true; } } return false; } if !master_value_exist && dev_value_exist { if is_manual { println!( "{} {}: master has no LastChange while dev has, return `TRUE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return true; } else if user_input.trim() == "n" { return false; } } return true; } if master_value_exist && !dev_value_exist { if is_manual { println!( "{} {}: dev has no LastChange while master has, return `FALSE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return false; } else if user_input.trim() == "n" { return true; } } return false; } let mut this_recipe_lastchange = DateTime::parse_from_str( self.LastChange.clone().unwrap().as_str().unwrap(), "%d-%m-%Y %H:%M:%S", ); let mut try_updated_lastchange = DateTime::parse_from_str( another_recipe01 .clone() .LastChange .unwrap() .as_str() .unwrap(), "%d-%m-%Y %H:%M:%S", ); // println!( // "{}: LastChange: {}", // self.productCode, // this_recipe_lastchange.unwrap().clone() // ); // println!( // "{}: LastChange: {}", // another_recipe01.productCode, // try_updated_lastchange.unwrap().clone() // ); let this_naive_lastchange_string = NaiveDateTime::parse_from_str( self.LastChange.clone().unwrap().as_str().unwrap(), "%d-%b-%Y %H:%M:%S", ); let try_naive_lastchange_string = NaiveDateTime::parse_from_str( another_recipe01 .clone() .LastChange .unwrap() .as_str() .unwrap(), "%d-%b-%Y %H:%M:%S", ); if this_recipe_lastchange.is_err() || try_updated_lastchange.is_err() { // try utc to datetime // println!( // "Error parsing LastChange {}", // this_recipe_lastchange.err().unwrap() // ); // println!( // "Error parsing LastChange {}", // try_updated_lastchange.err().unwrap() // ); this_recipe_lastchange = DateTime::parse_from_rfc3339(self.LastChange.clone().unwrap().as_str().unwrap()); try_updated_lastchange = DateTime::parse_from_rfc3339( another_recipe01 .clone() .LastChange .unwrap() .as_str() .unwrap(), ); // check if still err // println!( // "Persist err: {:?}, {:?}", // this_recipe_lastchange.err(), // try_updated_lastchange.err() // ); } if this_recipe_lastchange.is_ok() && try_updated_lastchange.is_ok() { let this_recipe_lastchange = this_recipe_lastchange.unwrap(); let try_updated_lastchange = try_updated_lastchange.unwrap(); println!("manual on? {}", is_manual); println!( "equal = {}", this_recipe_lastchange == try_updated_lastchange ); println!("less = {}", this_recipe_lastchange < try_updated_lastchange); println!( "greater = {}", this_recipe_lastchange > try_updated_lastchange ); if this_recipe_lastchange == try_updated_lastchange { if is_manual { println!( "{} {}: both LastChange is same, return `FALSE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return false; } else if user_input.trim() == "n" { return true; } } return false; } if this_recipe_lastchange < try_updated_lastchange { if is_manual { println!( "{} {}: dev's LastChange is newer, return `TRUE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return true; } else if user_input.trim() == "n" { return false; } } return true; } if this_recipe_lastchange > try_updated_lastchange { if is_manual { println!( "{} {}: dev's LastChange is older, return `FALSE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return false; } else if user_input.trim() == "n" { return true; } } return false; } } else if this_naive_lastchange_string.is_ok() && try_naive_lastchange_string.is_ok() { println!("do use naive."); let this_naive_lastchange_string = this_naive_lastchange_string.unwrap(); let try_naive_lastchange_string = try_naive_lastchange_string.unwrap(); if this_naive_lastchange_string == try_naive_lastchange_string { if is_manual { println!( "{} {}: both LastChange is same, return `FALSE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return false; } else if user_input.trim() == "n" { return true; } } return false; } if this_naive_lastchange_string < try_naive_lastchange_string { if is_manual { println!( "{} {}: dev's LastChange is newer, return `TRUE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return true; } else if user_input.trim() == "n" { return false; } } return true; } if this_naive_lastchange_string > try_naive_lastchange_string { if is_manual { println!( "{} {}: dev's LastChange is older, return `FALSE` (y/n/ignore)?", self.productCode, another_recipe01.productCode ); match io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { println!("error: {}", e); } } if user_input.eq_ignore_ascii_case("y") || user_input.eq_ignore_ascii_case("ignore") { return false; } else if user_input.trim() == "n" { return true; } } return false; } } println!("."); false } fn update_menu_by_field(&mut self, another_recipe01: Recipe01, field: String) -> Recipe01 { match field.as_str() { "recipes" => { update_each_field!(self, another_recipe01, recipes); } "ToppingSet" => { update_each_field!(self, another_recipe01, ToppingSet); } "Description" => { update_each_field!(self, another_recipe01, Description); } "LastChange" => { update_each_field!(self, another_recipe01, LastChange); } "StringParam" => { update_each_field!(self, another_recipe01, StringParam); } "TextForWarningBeforePay" => { update_each_field!(self, another_recipe01, TextForWarningBeforePay); } // "SubMenu" => { // update_each_field!(self, another_recipe01, SubMenu); // } "MenuStatus" => { update_each_field!(self, another_recipe01, MenuStatus); } "ExtendID" => { update_each_field!(self, another_recipe01, ExtendID); } "cashPrice" => { update_each_field!(self, another_recipe01, cashPrice); } "nonCashPrice" => { update_each_field!(self, another_recipe01, nonCashPrice); } "changerecipe" => { update_each_field!(self, another_recipe01, changerecipe); } // "disable" => { // update_each_field!(self, another_recipe01, disable); // } // "disable_by_cup" => { // update_each_field!(self, another_recipe01, disable_by_cup); // } // "disable_by_ice" => { // update_each_field!(self, another_recipe01, disable_by_ice); // } "EncoderCount" => { update_each_field!(self, another_recipe01, EncoderCount); } "id" => { update_each_field!(self, another_recipe01, id); } "isUse" => { update_each_field!(self, another_recipe01, isUse); } // "isShow" => { // update_each_field!(self, another_recipe01, isShow); // } "name" => { update_each_field!(self, another_recipe01, name); } "otherDescription" => { update_each_field!(self, another_recipe01, otherDescription); } "otherName" => { update_each_field!(self, another_recipe01, otherName); } "productCode" => { update_each_field!(self, another_recipe01, productCode); } "total_time" => { update_each_field!(self, another_recipe01, total_time); } "total_weight" => { update_each_field!(self, another_recipe01, total_weight); } "uriData" => { update_each_field!(self, another_recipe01, uriData); } "useGram" => { update_each_field!(self, another_recipe01, useGram); } "weight_float" => { update_each_field!(self, another_recipe01, weight_float); } "OnTOP" => { update_each_field!(self, another_recipe01, OnTOP); } _ => {} } self.to_owned() } fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; } Some(serde_json::to_value(self.extra.clone()).unwrap()) } } impl Recipe01 { pub fn to_map(&self) -> HashMap { serde_json::from_value(serde_json::json!(*self)).unwrap() } } /// The above code defines a struct named RecipeList with several fields of different types. /// /// Properties: /// /// * `MixOrder`: The MixOrder property is of type Value and represents the order in which the recipe /// ingredients should be mixed. /// * `StringParam`: StringParam is an optional value that can be of any type. It represents a string /// parameter for the recipe. /// * `FeedParameter`: The `FeedParameter` property is of type `Value`. It represents a parameter /// related to feeding in a recipe. /// * `FeedPattern`: The `FeedPattern` property is of type `Value`. It represents the pattern used for /// feeding in a recipe. /// * `isUse`: A boolean value indicating whether the recipe is being used or not. /// * `materialPathId`: The `materialPathId` property is of type `Value`. It represents the ID of the /// material path used in the recipe. /// * `powderGram`: The `powderGram` property represents the amount of powder (in grams) used in the /// recipe. /// * `powderTime`: The `powderTime` property represents the time required to add and mix the powder /// ingredient in a recipe. /// * `stirTime`: The `stirTime` property represents the time it takes to stir the ingredients in the /// recipe. /// * `syrupGram`: The `syrupGram` property represents the amount of syrup in grams used in a recipe. /// * `syrupTime`: The `syrupTime` property is of type `Value`. It represents the time in seconds for /// the syrup to be added in the recipe. /// * `waterCold`: The `waterCold` property is of type `Value` and represents the temperature of cold /// water in the recipe. /// * `waterYield`: The `waterYield` property represents the amount of water produced or obtained from /// the recipe. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct RecipeList { pub MixOrder: Value, #[serde(default = "BlankValueString")] pub StringParam: Option, pub FeedParameter: Value, pub FeedPattern: Value, pub isUse: bool, pub materialPathId: Value, pub powderGram: Value, pub powderTime: Value, pub stirTime: Value, pub syrupGram: Value, pub syrupTime: Value, pub waterCold: Value, pub waterYield: Value, #[serde(flatten)] pub extra: std::collections::HashMap, } pub enum MaterialType { Bean, Syrup, Soda, Powder, Water, Ice, Cup, Lid, Straw, Whipper, Leaves, Clean, CleanV2, Unknown, } impl RecipeList { pub fn get_material_type(&mut self) {} // grep data fn pub fn get_data_used_in_brew(&mut self) -> Value { if self.isUse {} Value::Null } pub fn compare(&self, another_recipe_list: &RecipeList) -> Vec { let mut diff_list = Vec::new(); compare_field!(self, another_recipe_list, diff_list, MixOrder); compare_field!(self, another_recipe_list, diff_list, StringParam); compare_field!(self, another_recipe_list, diff_list, FeedParameter); compare_field!(self, another_recipe_list, diff_list, FeedPattern); compare_field!(self, another_recipe_list, diff_list, isUse); compare_field!(self, another_recipe_list, diff_list, materialPathId); compare_field!(self, another_recipe_list, diff_list, powderGram); compare_field!(self, another_recipe_list, diff_list, powderTime); compare_field!(self, another_recipe_list, diff_list, stirTime); compare_field!(self, another_recipe_list, diff_list, syrupGram); compare_field!(self, another_recipe_list, diff_list, syrupTime); compare_field!(self, another_recipe_list, diff_list, waterCold); compare_field!(self, another_recipe_list, diff_list, waterYield); compare_field!(self, another_recipe_list, diff_list, extra); diff_list } pub fn to_map(&self) -> serde_json::Value { serde_json::to_value(self).unwrap() } } /// The above code defines a Rust struct called MaterialSetting with various fields representing /// different properties of a material. /// /// Properties: /// /// * `AlarmIDWhenOffline`: The AlarmIDWhenOffline property is of type Value and represents the ID of /// the alarm when the material is offline. /// * `BeanChannel`: A boolean value indicating whether the material has a bean channel. /// * `CanisterType`: An optional string that represents the type of canister. /// * `DrainTimer`: The `DrainTimer` property is of type `Value`. It represents the timer for draining /// the material. /// * `IceScreamBingsuChannel`: IceScreamBingsuChannel is an optional boolean property that represents /// whether the material has an IceScreamBingsu channel. /// * `IsEquipment`: A boolean value indicating whether the material is an equipment or not. /// * `LeavesChannel`: A boolean value indicating whether the material has a leaves channel. /// * `LowToOffline`: The `LowToOffline` property is of type `Value` and represents the value at which /// the material goes from a low status to an offline status. /// * `MaterialDescription`: An optional description of the material. /// * `MaterialStatus`: The MaterialStatus property represents the status of a material. It is of type /// Value, which could be any data type depending on the implementation. /// * `PowderChannel`: A boolean value indicating whether the material has a powder channel. /// * `RefillUnitGram`: A boolean value indicating whether the material can be refilled in grams. /// * `RefillUnitMilliliters`: RefillUnitMilliliters is a boolean property that indicates whether the /// material can be refilled in milliliters. If it is set to true, it means that the material can be /// refilled using milliliters as the unit of measurement. /// * `RefillUnitPCS`: RefillUnitPCS is a boolean property that indicates whether the material can be /// refilled in units of pieces (PCS). /// * `ScheduleDrainType`: The ScheduleDrainType property is of type Value. /// * `SodaChannel`: A boolean value indicating whether the material has a soda channel. /// * `StrTextShowError`: An optional vector of strings that represents any error messages related to /// the material setting. /// * `SyrupChannel`: A boolean value indicating whether the material has a syrup channel. /// * `id`: The `id` property is of type `Value` and represents the unique identifier of the material /// setting. /// * `idAlternate`: The `idAlternate` property is of type `Value` and represents an alternate ID for /// the material setting. /// * `isUse`: A boolean value indicating whether the material is in use or not. /// * `materialOtherName`: The `materialOtherName` property is a string that represents an alternate /// name for the material. /// * `materialName`: The name of the material. /// * `pathOtherName`: The property "pathOtherName" is an optional string that represents an alternate /// name for the material's path. /// * `pay_rettry_max_count`: The `pay_rettry_max_count` property is of type `Value` and represents the /// maximum number of retries for a payment. /// * `RawMaterialUnit`: The `RawMaterialUnit` property is an optional string that represents the unit /// of measurement for the raw material. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MaterialSetting { pub AlarmIDWhenOffline: Value, pub BeanChannel: bool, #[serde(default = "BlankString")] pub CanisterType: Option, pub DrainTimer: Value, #[serde(default = "BlankBool")] pub IceScreamBingsuChannel: Option, pub IsEquipment: bool, pub LeavesChannel: bool, pub LowToOffline: Value, #[serde(default = "BlankString")] pub MaterialDescription: Option, pub MaterialStatus: Value, pub PowderChannel: bool, pub RefillUnitGram: bool, pub RefillUnitMilliliters: bool, pub RefillUnitPCS: bool, pub ScheduleDrainType: Value, pub SodaChannel: bool, #[serde(default = "BlankOtherStrShowTextError")] pub StrTextShowError: Option>, pub SyrupChannel: bool, pub id: Value, pub idAlternate: Value, pub isUse: bool, #[serde(default = "BlankString")] pub materialOtherName: Option, #[serde(default = "BlankString")] pub materialName: Option, #[serde(default = "BlankString")] pub pathOtherName: Option, pub pay_rettry_max_count: Value, #[serde(default = "BlankString")] pub RawMaterialUnit: Option, #[serde(default = "BlankString")] pub MaterialParameter: Option, #[serde(flatten)] pub extra: std::collections::HashMap, } impl CommonRecipeTrait for MaterialSetting { fn insert_comment(&mut self, comment: String) { // self.MaterialDescription = Some(comment); } fn compare(&self, another_setting: &Self) -> Vec { let mut list = Vec::new(); compare_field!(self, another_setting, list, MaterialDescription); compare_field!(self, another_setting, list, id); compare_field!(self, another_setting, list, idAlternate); compare_field!(self, another_setting, list, isUse); compare_field!(self, another_setting, list, materialOtherName); compare_field!(self, another_setting, list, materialName); compare_field!(self, another_setting, list, pathOtherName); compare_field!(self, another_setting, list, pay_rettry_max_count); compare_field!(self, another_setting, list, RawMaterialUnit); compare_field!(self, another_setting, list, MaterialStatus); compare_field!(self, another_setting, list, AlarmIDWhenOffline); compare_field!(self, another_setting, list, CanisterType); compare_field!(self, another_setting, list, RefillUnitGram); compare_field!(self, another_setting, list, RefillUnitMilliliters); compare_field!(self, another_setting, list, RefillUnitPCS); compare_field!(self, another_setting, list, ScheduleDrainType); compare_field!(self, another_setting, list, SyrupChannel); compare_field!(self, another_setting, list, PowderChannel); compare_field!(self, another_setting, list, SodaChannel); compare_field!(self, another_setting, list, BeanChannel); compare_field!(self, another_setting, list, LeavesChannel); compare_field!(self, another_setting, list, LowToOffline); compare_field!(self, another_setting, list, SodaChannel); list } } impl MaterialSetting { pub fn get_definition_type(&mut self) -> MaterialType { match self.id.as_i64().unwrap() { 8102..8103 => MaterialType::Whipper, 9100 => MaterialType::Ice, 8001 => MaterialType::Clean, 8002 => MaterialType::CleanV2, _ => { if self.BeanChannel { MaterialType::Bean } else if self.PowderChannel { MaterialType::Powder } else if self.SyrupChannel { MaterialType::Syrup } else if self.SodaChannel { MaterialType::Soda } else { MaterialType::Unknown } } } } } impl MachineSetting { pub fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; } Some(serde_json::to_value(self.extra.clone()).unwrap()) } } #[allow(non_snake_case)] fn BlankListGroupID() -> Option> { Some(vec![ Value::String("0".into()), Value::String("0".into()), Value::String("0".into()), Value::String("0".into()), ]) } #[allow(non_snake_case)] fn BlankGroupID() -> Option { Some(Value::String("0".into())) } /// The above code defines a struct named `MenuToppingList` with several fields. /// /// Properties: /// /// * `ListGroupID`: An optional vector of values representing the list group IDs. /// * `defaultIDSelect`: The `defaultIDSelect` property is of type `Value` and represents the default ID /// selected for the menu topping list. /// * `groupID`: The `groupID` property is an optional field that represents the ID of the group to /// which the menu topping list belongs. /// * `isUse`: The `isUse` property is a boolean value that indicates whether the menu topping list is /// being used or not. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MenuToppingList { #[serde(default = "BlankListGroupID")] pub ListGroupID: Option>, pub defaultIDSelect: Value, #[serde(default = "BlankGroupID")] pub groupID: Option, pub isUse: bool, // #[serde(flatten)] // pub extra: std::collections::HashMap, } /// The `Topping` struct represents a collection of topping groups and topping lists. /// /// Properties: /// /// * `ToppingGroup`: ToppingGroup is a vector (array) that contains instances of the ToppingGroup /// struct. Each ToppingGroup struct represents a group of toppings. /// * `ToppingList`: The `ToppingList` property is a vector (dynamic array) that contains elements of /// type `ToppingList`. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Topping { pub ToppingGroup: Vec, pub ToppingList: Vec, #[serde(flatten)] pub extra: std::collections::HashMap, } /// The above code defines a struct named ToppingGroup with several fields. /// /// Properties: /// /// * `groupID`: The `groupID` property is of type `Value`. It represents the ID of the topping group. /// * `idDefault`: The `idDefault` property is of type `Value`. It represents the default ID for the /// topping group. /// * `idInGroup`: The `idInGroup` property is a string that represents the ID of the topping within the /// group. /// * `inUse`: The `inUse` property is a boolean value that indicates whether the topping group is /// currently in use or not. If `inUse` is `true`, it means the topping group is being used. If `inUse` /// is `false`, it means the topping group is not being used. /// * `name`: The `name` property is a string that represents the name of the topping group. /// * `otherName`: The `otherName` property is a string that represents an alternative name for the /// `ToppingGroup`. It can be used to provide additional information or a different name for the group. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ToppingGroup { pub groupID: Value, pub idDefault: Value, pub idInGroup: String, pub inUse: bool, pub name: String, pub otherName: String, #[serde(flatten)] pub extra: std::collections::HashMap, } impl ToppingGroup { pub fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; } Some(serde_json::to_value(self.extra.clone()).unwrap()) } } /// The above code defines a Rust struct called ToppingList with various fields and their corresponding /// data types. /// /// Properties: /// /// * `ExtendID`: The ExtendID property is of type Value and represents the extended ID of the topping /// list. /// * `OnTOP`: A boolean value indicating whether the topping should be placed on top of the item. /// * `MenuStatus`: The MenuStatus property is of type Value and represents the status of the menu. /// * `cashPrice`: The `cashPrice` property is of type `Value` and represents the price of the topping /// in cash. /// * `disable`: A boolean value indicating whether the topping is disabled or not. If it is set to /// true, it means the topping is disabled and cannot be used. If it is set to false, it means the /// topping is enabled and can be used. /// * `disable_by_cup`: A boolean value indicating whether the topping is disabled based on the cup /// size. /// * `disable_by_ice`: The `disable_by_ice` property is a boolean value that indicates whether the /// topping is disabled based on the ice level. If it is set to `true`, it means that the topping cannot /// be added when the ice level is selected. If it is set to `false`, it means that the /// * `EncoderCount`: The `EncoderCount` property is of type `Value` and represents the count of /// encoders. /// * `id`: The unique identifier for the ToppingList object. /// * `isUse`: A boolean value indicating whether the topping is in use or not. /// * `isShow`: A boolean value indicating whether the topping is shown or hidden in the menu. /// * `name`: The name of the topping list item. It is an optional string value. /// * `nonCashPrice`: The `nonCashPrice` property is of type `Value` and represents the non-cash price /// of the topping. /// * `otherName`: The `otherName` property is an optional field that represents an alternative name for /// the topping. It is of type `Option`, which means it can either be `Some(String)` if a value /// is present, or `None` if no value is provided. /// * `productCode`: The `productCode` property is an optional string that represents the code of the /// topping in the menu. /// * `recipes`: A vector of RecipeList structs. /// * `total_time`: The `total_time` property represents the total time required for the topping in some /// unit of measurement. /// * `total_weight`: The `total_weight` property represents the total weight of the topping list. /// * `useGram`: A boolean value indicating whether the topping uses grams for measurement or not. /// * `weight_float`: The `weight_float` property is of type `Value` and represents the weight of the /// topping in float format. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ToppingList { pub ExtendID: Value, pub OnTOP: bool, pub MenuStatus: Value, pub cashPrice: Value, // pub disable: bool, // pub disable_by_cup: bool, // pub disable_by_ice: bool, pub EncoderCount: Value, pub id: Value, pub isUse: bool, // pub isShow: bool, #[serde(default = "BlankString")] pub name: Option, pub nonCashPrice: Value, #[serde(default = "BlankString")] pub otherName: Option, #[serde(default = "BlankString")] pub productCode: Option, pub recipes: Vec, pub total_time: Value, pub total_weight: Value, pub useGram: bool, pub weight_float: Value, #[serde(flatten)] pub extra: std::collections::HashMap, } impl ToppingList { pub fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; } Some(serde_json::to_value(self.extra.clone()).unwrap()) } } /// The above type represents a material code with package description, refill value per step, material /// ID, and material code. /// /// Properties: /// /// * `PackageDescription`: An optional string that describes the package of the material. /// * `RefillValuePerStep`: RefillValuePerStep is a property of type Value. It represents the refill /// value per step for a material. /// * `materialID`: The `materialID` property is of type `Value`. It represents the unique identifier /// for a material. /// * `materialCode`: The `materialCode` property is an optional field that represents the code /// associated with a material. It is of type `Option`, which means it can either be /// `Some(String)` if a value is present, or `None` if no value is provided. #[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MaterialCode { #[serde(default = "BlankString")] pub PackageDescription: Option, pub RefillValuePerStep: Value, pub materialID: Value, #[serde(default = "BlankString")] pub materialCode: Option, #[serde(flatten)] pub extra: std::collections::HashMap, } impl MaterialCode { pub fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; } Some(serde_json::to_value(self.extra.clone()).unwrap()) } } impl Recipe { /// The function `list_menu_product_code` returns a vector of product codes from a list of recipes. /// /// Returns: /// /// a vector of string references (`&str`) representing the product codes of the recipes in /// `Recipe01`. pub fn list_menu_product_code(&self) -> Vec<&str> { self.Recipe01 .iter() .map(|r| r.productCode.as_str()) .collect() } /// The function `list_material_settings` returns a vector of strings containing the IDs of material /// settings. /// /// Returns: /// /// The function `list_material_settings` returns a `Vec` which contains the IDs of the material /// settings. pub fn list_material_settings(&self) -> Vec { self.MaterialSetting .iter() .map(|r| r.id.to_string()) .collect() } pub fn list_topping_list(&self) -> Vec { self.Topping .ToppingList .iter() .map(|r| r.id.clone().to_string()) .collect() } pub fn list_topping_group(&self) -> Vec { self.Topping .ToppingGroup .iter() .map(|r| r.groupID.clone().to_string()) .collect() } #[cfg(feature = "diff")] pub fn list_diff_pd_between_recipes(&self, another_recipe: &Recipe) -> Vec { let mut list = Vec::new(); self.Recipe01.iter().for_each(|r| { if !another_recipe.diff_pd_between_recipes(another_recipe, r.productCode.clone()) { list.push(r.productCode.clone()); } }); list } pub fn list_diff_pd_ignore_country_code(&self, another_recipe: &Recipe) -> Vec { let mut list = Vec::new(); self.Recipe01.iter().for_each(|r| { // clean before search let rpl = r.clone(); // grep only recipe without country code let pd = rpl.productCode; let pd_split = pd.split("-").collect::>(); // combine without first elem let mut new_pd = String::new(); for (i, ele) in pd_split.iter().enumerate() { if i != 0 { new_pd.push_str(ele); if i != pd_split.len() - 1 { new_pd.push('-'); } } } if !another_recipe .diff_pd_between_recipes_ignore_country(another_recipe, new_pd.clone()) { list.push(new_pd.clone()); } }); list } pub fn find_recipe_by_material_path_id(&self, material_path_id: &str) -> Vec<&Recipe01> { // let mut res = Vec::new(); let total: Vec<&Recipe01> = self .Recipe01 .iter() .filter(|r| { for ele in r.recipes.clone() { if ele.materialPathId.as_i64().unwrap() == material_path_id.parse::().unwrap() { // res.push(r.clone()); return true; } } return false; }) .collect(); total } } impl Recipe01 { pub fn list_recipe_only_id(&self) -> Vec { self.recipes .iter() .map(|rpl| rpl.materialPathId.clone().to_string()) .collect() } }