add deep diff pd
This commit is contained in:
parent
64a7dae017
commit
6ccc6cb779
5 changed files with 67 additions and 53 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -143,7 +143,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
|||
|
||||
[[package]]
|
||||
name = "libtbr"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "libtbr"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -8,4 +8,4 @@ chrono = "0.4.41"
|
|||
log = "0.4.27"
|
||||
rayon = "1.10.0"
|
||||
serde = { version = "1.0.219", features = ["derive", "serde_derive"] }
|
||||
serde_json = "1.0.140"
|
||||
serde_json = "1.0.140"
|
||||
49
README.md
49
README.md
|
|
@ -25,55 +25,6 @@ let recipe_dir = cfg.get("RECIPE_DIR").unwrap();
|
|||
|
||||
---
|
||||
|
||||
** Helper functions **
|
||||
|
||||
This will be included in the next version. So the following functions may just have to implement by yourself for now.
|
||||
|
||||
```rust
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Error, Read};
|
||||
|
||||
/// Get valid country names
|
||||
fn valid_country_name() -> Vec<&'static str> {
|
||||
vec!["mys", "sgp", "aus", "tha", "hkg", "dubai", "uae"]
|
||||
}
|
||||
|
||||
/// Get latest versions of recipes
|
||||
fn grep_latest_versions(dir_path: &str) -> Result<HashMap<String, usize>, io::Error> {
|
||||
let mut vs = HashMap::new();
|
||||
|
||||
// open dir
|
||||
let entries = std::fs::read_dir(dir_path)?
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.collect::<Result<Vec<_>, io::Error>>()?;
|
||||
|
||||
for e in entries {
|
||||
let path = e.clone().to_str().unwrap().to_string();
|
||||
let mut cl_path = path.clone();
|
||||
let path_split = path.split("/").collect::<Vec<&str>>();
|
||||
let dir_name = path_split[path_split.len() - 1];
|
||||
|
||||
if valid_country_name().contains(&dir_name) {
|
||||
cl_path.push_str("/version");
|
||||
|
||||
// read filename
|
||||
let mut file = File::open(cl_path)?;
|
||||
let mut data = String::new();
|
||||
file.read_to_string(&mut data).unwrap();
|
||||
|
||||
vs.insert(dir_name.to_string(), data.parse::<usize>().unwrap());
|
||||
}
|
||||
|
||||
// expect dir with country
|
||||
}
|
||||
|
||||
Ok(vs)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Get recipe from specific country (latest)
|
||||
```rust
|
||||
...
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ macro_rules! update_each_field {
|
|||
/// 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,
|
||||
|
|
@ -91,6 +92,8 @@ impl Recipe {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
|
@ -115,12 +118,14 @@ impl Recipe {
|
|||
.find(|&r| 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| 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();
|
||||
|
||||
|
|
@ -136,6 +141,7 @@ impl Recipe {
|
|||
found
|
||||
}
|
||||
|
||||
/// Get position of product code in the recipe's `Recipe01`
|
||||
pub fn get_pd_index(&self, product_code: String) -> usize {
|
||||
self.Recipe01
|
||||
.iter()
|
||||
|
|
@ -143,26 +149,32 @@ impl Recipe {
|
|||
.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());
|
||||
|
|
@ -177,6 +189,7 @@ impl Recipe {
|
|||
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,
|
||||
|
|
@ -195,6 +208,35 @@ impl Recipe {
|
|||
menu1 == menu2
|
||||
}
|
||||
|
||||
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) {
|
||||
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,
|
||||
|
|
@ -522,6 +564,7 @@ impl Recipe {
|
|||
pub fn add_prefix_country_code(&mut self) {}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn StrShowTextErrorDefault() -> Option<Vec<Value>> {
|
||||
Some(vec![
|
||||
Value::String("เต่าบินเกิดเหตุขัดข้อง".into()),
|
||||
|
|
@ -569,6 +612,7 @@ impl PartialRecipe {
|
|||
/// 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>>,
|
||||
|
|
@ -659,26 +703,32 @@ impl CommonRecipeTrait for MachineSetting {
|
|||
}
|
||||
}
|
||||
|
||||
#[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(),
|
||||
|
|
@ -692,14 +742,17 @@ fn BlankOtherStrShowTextError() -> Option<Vec<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()))
|
||||
}
|
||||
|
|
@ -754,6 +807,7 @@ fn BlankValueBool() -> Option<Value> {
|
|||
/// * `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")]
|
||||
|
|
@ -1371,6 +1425,7 @@ impl Recipe01 {
|
|||
/// 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,
|
||||
|
|
@ -1465,6 +1520,7 @@ impl RecipeList {
|
|||
/// 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,
|
||||
|
|
@ -1576,6 +1632,7 @@ impl MachineSetting {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn BlankListGroupID() -> Option<Vec<Value>> {
|
||||
Some(vec![
|
||||
Value::String("0".into()),
|
||||
|
|
@ -1585,6 +1642,7 @@ fn BlankListGroupID() -> Option<Vec<Value>> {
|
|||
])
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn BlankGroupID() -> Option<Value> {
|
||||
Some(Value::String("0".into()))
|
||||
}
|
||||
|
|
@ -1600,6 +1658,7 @@ fn BlankGroupID() -> Option<Value> {
|
|||
/// 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")]
|
||||
|
|
@ -1620,6 +1679,7 @@ pub struct MenuToppingList {
|
|||
/// 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>,
|
||||
|
|
@ -1643,6 +1703,7 @@ pub struct Topping {
|
|||
/// * `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,
|
||||
|
|
@ -1703,6 +1764,7 @@ impl ToppingGroup {
|
|||
/// * `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,
|
||||
|
|
@ -1754,6 +1816,7 @@ impl ToppingList {
|
|||
/// * `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")]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::collections::HashMap;
|
|||
use crate::models::recipe::*;
|
||||
use crate::recipe_functions::common::*;
|
||||
|
||||
/// Version 2 of `models::Recipe`
|
||||
/// Version 2 of `models::Recipe`, WIP
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct VersionRecipe {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue