libtbr/src/models/recipe.rs

2048 lines
74 KiB
Rust

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<String>;
}
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<Recipe01>,
pub MaterialSetting: Vec<MaterialSetting>,
pub Topping: Topping,
pub MaterialCode: Vec<MaterialCode>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, serde_json::Value>,
}
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<Value> {
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<String>) -> Vec<Option<Recipe01>> {
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<String> {
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<String> {
// 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<Value> {
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<String>,
) {
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<String>) {
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<i32>,
) -> Option<Value> {
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::<Value>(),
),
_ => 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<String> {
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<String> {
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<String> {
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<Vec<Value>> {
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<Recipe01>,
pub material_settings: Vec<MaterialSetting>,
}
impl PartialRecipe {
pub fn sync_new(&mut self, new_recipe: Vec<Recipe01>, new_material: Vec<MaterialSetting>) {
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<Vec<String>>,
pub RecipeTag: String,
#[serde(default = "StrShowTextErrorDefault")]
pub StrTextShowError: Option<Vec<Value>>,
pub configNumber: Value,
pub temperatureMax: Value,
pub temperatureMin: Value,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, serde_json::Value>,
}
pub trait MachineSettingTrait {
fn get_comment(&self) -> Vec<String>;
fn get_recipe_tag(&self) -> String;
fn get_str_text_show_error(&self) -> Option<Vec<Value>>;
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<String> {
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<Vec<Value>> {
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<String> {
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<String> {
Some("".to_string())
}
#[allow(non_snake_case)]
fn BlankBool() -> Option<bool> {
Some(false)
}
#[allow(non_snake_case)]
fn BlankVecString() -> Option<Vec<String>> {
Some(vec![].into())
}
#[allow(non_snake_case)]
fn BlankVecRecipe() -> Option<Vec<Recipe01>> {
Some(vec![].into())
}
#[allow(non_snake_case)]
fn BlankVecMenuTopping() -> Option<Vec<MenuToppingList>> {
Some(vec![].into())
}
#[allow(non_snake_case)]
fn BlankOtherStrShowTextError() -> Option<Vec<String>> {
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<Value> {
Some(Value::String("".into()))
}
#[allow(non_snake_case, dead_code)]
fn BlankValueArray() -> Option<Value> {
Some(Value::Array(vec![].into()))
}
#[allow(non_snake_case, dead_code)]
fn BlankValueBool() -> Option<Value> {
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<String>`, 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<String>,
pub ExtendID: Value,
pub OnTOP: Value,
#[serde(default = "BlankValueString")]
pub LastChange: Option<Value>,
pub MenuStatus: Value,
#[serde(default = "BlankValueString")]
pub StringParam: Option<Value>,
#[serde(default = "BlankVecString")]
pub TextForWarningBeforePay: Option<Vec<String>>,
pub cashPrice: Value,
#[serde(default = "BlankString")]
pub changerecipe: Option<String>,
// 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<String>,
pub nonCashPrice: Value,
#[serde(default = "BlankString")]
pub otherDescription: Option<String>,
#[serde(default = "BlankString")]
pub otherName: Option<String>,
pub productCode: String,
pub recipes: Vec<RecipeList>,
#[serde(default = "BlankVecRecipe")]
pub SubMenu: Option<Vec<Recipe01>>,
#[serde(default = "BlankVecMenuTopping")]
pub ToppingSet: Option<Vec<MenuToppingList>>,
pub total_time: Value,
pub total_weight: Value,
#[serde(default = "BlankString")]
pub uriData: Option<String>,
pub useGram: bool,
pub weight_float: Value,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, Value>,
}
pub trait Recipe01Trait {
fn insert_sub_menu(&mut self, sub_menu: Recipe01);
fn compare(&self, another_recipe01: &Recipe01) -> Vec<String>;
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<Value>;
}
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<String> {
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::<Vec<String>>();
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<Value> {
if self.extra.is_empty() {
return None;
}
Some(serde_json::to_value(self.extra.clone()).unwrap())
}
}
impl Recipe01 {
pub fn to_map(&self) -> HashMap<String, Value> {
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<Value>,
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<String, Value>,
}
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<String> {
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<String>,
pub DrainTimer: Value,
#[serde(default = "BlankBool")]
pub IceScreamBingsuChannel: Option<bool>,
pub IsEquipment: bool,
pub LeavesChannel: bool,
pub LowToOffline: Value,
#[serde(default = "BlankString")]
pub MaterialDescription: Option<String>,
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<Vec<String>>,
pub SyrupChannel: bool,
pub id: Value,
pub idAlternate: Value,
pub isUse: bool,
#[serde(default = "BlankString")]
pub materialOtherName: Option<String>,
#[serde(default = "BlankString")]
pub materialName: Option<String>,
#[serde(default = "BlankString")]
pub pathOtherName: Option<String>,
pub pay_rettry_max_count: Value,
#[serde(default = "BlankString")]
pub RawMaterialUnit: Option<String>,
#[serde(default = "BlankString")]
pub MaterialParameter: Option<String>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, Value>,
}
impl CommonRecipeTrait for MaterialSetting {
fn insert_comment(&mut self, comment: String) {
// self.MaterialDescription = Some(comment);
}
fn compare(&self, another_setting: &Self) -> Vec<String> {
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<Value> {
if self.extra.is_empty() {
return None;
}
Some(serde_json::to_value(self.extra.clone()).unwrap())
}
}
#[allow(non_snake_case)]
fn BlankListGroupID() -> Option<Vec<Value>> {
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<Value> {
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<Vec<Value>>,
pub defaultIDSelect: Value,
#[serde(default = "BlankGroupID")]
pub groupID: Option<Value>,
pub isUse: bool,
// #[serde(flatten)]
// pub extra: std::collections::HashMap<String, Value>,
}
/// 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<ToppingGroup>,
pub ToppingList: Vec<ToppingList>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, Value>,
}
/// 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<String, Value>,
}
impl ToppingGroup {
pub fn get_additional_fields(&self) -> Option<Value> {
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<String>`, 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<String>,
pub nonCashPrice: Value,
#[serde(default = "BlankString")]
pub otherName: Option<String>,
#[serde(default = "BlankString")]
pub productCode: Option<String>,
pub recipes: Vec<RecipeList>,
pub total_time: Value,
pub total_weight: Value,
pub useGram: bool,
pub weight_float: Value,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, Value>,
}
impl ToppingList {
pub fn get_additional_fields(&self) -> Option<Value> {
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<String>`, 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<String>,
pub RefillValuePerStep: Value,
pub materialID: Value,
#[serde(default = "BlankString")]
pub materialCode: Option<String>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, Value>,
}
impl MaterialCode {
pub fn get_additional_fields(&self) -> Option<Value> {
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<String>` which contains the IDs of the material
/// settings.
pub fn list_material_settings(&self) -> Vec<String> {
self.MaterialSetting
.iter()
.map(|r| r.id.to_string())
.collect()
}
pub fn list_topping_list(&self) -> Vec<String> {
self.Topping
.ToppingList
.iter()
.map(|r| r.id.clone().to_string())
.collect()
}
pub fn list_topping_group(&self) -> Vec<String> {
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<String> {
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<String> {
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::<Vec<&str>>();
// 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::<i64>().unwrap()
{
// res.push(r.clone());
return true;
}
}
return false;
})
.collect();
total
}
}
impl Recipe01 {
pub fn list_recipe_only_id(&self) -> Vec<String> {
self.recipes
.iter()
.map(|rpl| rpl.materialPathId.clone().to_string())
.collect()
}
}