1898 lines
69 KiB
Rust
1898 lines
69 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());
|
||
|
|
}
|
||
|
|
println!(
|
||
|
|
"check {}, {} | {:#?} != {:#?}",
|
||
|
|
stringify!($field).to_string(),
|
||
|
|
$self.$field != $another_setting.$field,
|
||
|
|
$self.$field,
|
||
|
|
$another_setting.$field
|
||
|
|
)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
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.
|
||
|
|
#[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!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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| r.productCode == product_code)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn search_pd_by_no_country_code(&self, product_code: String) -> Option<&Recipe01> {
|
||
|
|
self.Recipe01
|
||
|
|
.iter()
|
||
|
|
.find(|&r| r.productCode.contains(&product_code))
|
||
|
|
}
|
||
|
|
|
||
|
|
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| x.productCode.contains(pd))
|
||
|
|
.cloned()
|
||
|
|
})
|
||
|
|
.collect_into_vec(&mut found);
|
||
|
|
|
||
|
|
found
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn get_pd_index(&self, product_code: String) -> usize {
|
||
|
|
self.Recipe01
|
||
|
|
.iter()
|
||
|
|
.position(|r| r.productCode == product_code)
|
||
|
|
.unwrap()
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn search_material_settings(&self, material_code: String) -> Option<&MaterialSetting> {
|
||
|
|
self.MaterialSetting
|
||
|
|
.iter()
|
||
|
|
.find(|r| r.id.to_string() == material_code)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn search_topping_list(&self, id: String) -> Option<&ToppingList> {
|
||
|
|
self.Topping.ToppingList.iter().find(|&r| r.id == id)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn search_topping_group(&self, id: String) -> Option<&ToppingGroup> {
|
||
|
|
self.Topping.ToppingGroup.iter().find(|&r| r.groupID == id)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn search_material_code(&self, material_code: String) -> Option<&MaterialCode> {
|
||
|
|
self.MaterialCode
|
||
|
|
.iter()
|
||
|
|
.find(|&r| r.materialID == material_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
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
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) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
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.
|
||
|
|
#[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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankString() -> Option<String> {
|
||
|
|
Some("".to_string())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankBool() -> Option<bool> {
|
||
|
|
Some(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankVecString() -> Option<Vec<String>> {
|
||
|
|
Some(vec![].into())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankVecRecipe() -> Option<Vec<Recipe01>> {
|
||
|
|
Some(vec![].into())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankVecMenuTopping() -> Option<Vec<MenuToppingList>> {
|
||
|
|
Some(vec![].into())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankOtherStrShowTextError() -> Option<Vec<String>> {
|
||
|
|
Some(vec![
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
"".to_string(),
|
||
|
|
])
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankValueString() -> Option<Value> {
|
||
|
|
Some(Value::String("".into()))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankValueArray() -> Option<Value> {
|
||
|
|
Some(Value::Array(vec![].into()))
|
||
|
|
}
|
||
|
|
|
||
|
|
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.
|
||
|
|
#[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);
|
||
|
|
compare_field!(self, another_recipe01, list, recipes);
|
||
|
|
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.
|
||
|
|
#[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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 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.
|
||
|
|
#[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())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn BlankListGroupID() -> Option<Vec<Value>> {
|
||
|
|
Some(vec![
|
||
|
|
Value::String("0".into()),
|
||
|
|
Value::String("0".into()),
|
||
|
|
Value::String("0".into()),
|
||
|
|
Value::String("0".into()),
|
||
|
|
])
|
||
|
|
}
|
||
|
|
|
||
|
|
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.
|
||
|
|
#[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`.
|
||
|
|
#[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.
|
||
|
|
#[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.
|
||
|
|
#[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.
|
||
|
|
#[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()
|
||
|
|
}
|
||
|
|
}
|