v0.1 init

This commit is contained in:
Pakin 2025-05-26 12:39:09 +07:00
commit 8068810af6
18 changed files with 3859 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

402
Cargo.lock generated Normal file
View file

@ -0,0 +1,402 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libtbr"
version = "0.1.0"
dependencies = [
"chrono",
"log",
"rayon",
"serde",
"serde_json",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
dependencies = [
"windows-link",
]

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "libtbr"
version = "0.1.0"
edition = "2024"
[dependencies]
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"

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

3
src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod models;
pub mod previews;
pub mod recipe_functions;

2
src/models/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod recipe;
pub mod recipev2;

1897
src/models/recipe.rs Normal file

File diff suppressed because it is too large Load diff

93
src/models/recipev2.rs Normal file
View file

@ -0,0 +1,93 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::models::recipe::*;
use crate::recipe_functions::common::*;
/// Version 2 of `models::Recipe`
///
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VersionRecipe {
pub currentVersion: Value,
pub knownVersions: Vec<Value>,
pub recipesByCategory: HashMap<String, Vec<RecipeV2>>,
}
impl VersionRecipe {
// import - read all file with given format from directory and mixing them together as 1 recipe
pub fn create_versioning_by_country(country: &str) -> Self {
let mut path = String::from(RECIPES_DIR);
path.push_str(country);
path.push('/');
// read dir
let files = read_all_files_in_directory(&path.clone());
let expected_file_format = "coffeethai02";
let filtered = filter_files_by_pattern(files, expected_file_format);
// create recipes vector
let mut recipes = Vec::new();
let mut known_versions = Vec::new();
for file in filtered {
// try create model
let mut source_path = path.clone();
source_path.push_str(&file);
let current_recipe_path = create_recipe_model_from_file(source_path);
known_versions.push(current_recipe_path.clone().MachineSetting.configNumber);
recipes.push(current_recipe_path.clone());
}
// find current
//
let versions = grep_latest_versions(&path.clone()).unwrap();
let expected_version = versions.get(country).unwrap_or(&0);
VersionRecipe {
currentVersion: serde_json::json!(format!("{}", expected_version)),
knownVersions: known_versions,
recipesByCategory: break_down_recipe_into_category(recipes),
}
}
// export
// export_full
// export_full_with_history
}
pub fn break_down_recipe_into_category(recipes: Vec<Recipe>) -> HashMap<String, Vec<RecipeV2>> {
let mut result = HashMap::new();
// Steps
// 1.
result
}
/// Based on `models::Recipe`, this excluded some fields
/// for more efficient serialization and deserialization.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RecipeV2 {
pub Description: Option<String>,
pub LastChange: Option<Value>,
pub id: Value,
pub isUse: bool,
pub name: Option<String>,
pub otherName: Option<String>,
pub productCode: String,
pub recipes: Vec<RecipeListWithHistory>,
pub ToppingSet: Option<Vec<MenuToppingListWithHistory>>,
pub total_time: Value,
pub changeLog: Vec<HashMap<String, Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RecipeListWithHistory {
pub recipeList: RecipeList,
pub history: Vec<HashMap<String, Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MenuToppingListWithHistory {
pub menuToppingList: MenuToppingList,
pub history: Vec<HashMap<String, Value>>,
}

97
src/previews/csvf.rs Normal file
View file

@ -0,0 +1,97 @@
// format function
//
// target csv
use log::warn;
#[derive(Debug, Default, Clone)]
pub enum AutoFixSizeStrategy {
#[default]
Strict,
FillHeaderToMatchRow {
default_value: String,
},
FillRowToMatchHeader {
default_value: String,
},
}
#[derive(Debug, Default, Clone)]
pub struct TableConfig {
pub allow_row_length_exceeds_header: bool,
pub auto_fix_size: bool,
pub auto_fix_size_strategy: AutoFixSizeStrategy,
}
#[derive(Default, Clone)]
pub struct Table {
pub header: Vec<String>,
pub rows: Vec<Vec<String>>,
pub config: TableConfig,
}
impl Table {
pub fn new() -> Table {
Table::default()
}
pub fn set_config(&mut self, config: TableConfig) {
self.config = config;
}
pub fn add_header(&mut self, header: &str) {
self.header.push(header.to_string());
}
pub fn set_header(&mut self, header: Vec<&str>) {
self.header = header
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>();
}
pub fn get_current_header(self) -> Vec<String> {
self.header
}
pub fn add_row(&mut self, mut row: Vec<String>) {
if (self.clone().config.auto_fix_size) {
match self.clone().config.auto_fix_size_strategy {
AutoFixSizeStrategy::Strict => {
if (row.len() != self.header.len()) {
panic!("Row length does not match header length");
}
}
AutoFixSizeStrategy::FillHeaderToMatchRow { default_value } => {
if (row.len() > self.header.len()) {
warn!("Row length exceeds header length");
}
self.add_header(&default_value);
}
AutoFixSizeStrategy::FillRowToMatchHeader { default_value } => {
if (row.len() < self.header.len()) {
warn!("Row length is less than header length");
}
row.push(default_value.to_string());
}
}
}
self.rows.push(row);
}
pub fn add_column_desciption(&mut self, row_pos: usize, row: Vec<String>) {
self.rows.insert(row_pos, row);
}
pub fn generate_save_file(&mut self, output: &str) {
let mut csv = String::new();
csv.push_str(&self.header.join("\t"));
csv.push('\n');
for row in &self.rows {
csv.push_str(&row.join("\t"));
csv.push('\n');
}
std::fs::write(output, csv).unwrap();
}
}

255
src/previews/header.rs Normal file
View file

@ -0,0 +1,255 @@
// sample header for csvf
pub struct Headers {}
impl Headers {
// will be deprecated by next version
pub fn get_recipe_table_sgp_24022025() -> Vec<&'static str> {
[
"Name",
"ProductCode",
"521001",
"521002",
"521004",
"521005",
"521006",
"521003",
"521007",
"521008",
"521009",
"521101",
"521102",
"521103",
"521104",
"521105",
"521106",
"521107",
"521108",
"521020",
"521021",
"521022",
"521023",
"521024",
"521025",
"521027",
"521028",
"521029",
"521030",
"521200",
"521201",
"521202",
"521203",
"521205",
"521206",
"521207",
"521208",
"521210",
"521211",
"521212",
"521213",
"521214",
"521215",
"521216",
"521301",
"521041",
"521042",
"521043",
"521044",
"521045",
"521046",
"521048",
"521047",
"521049",
"521050",
"521051",
"521052",
"521053",
"521054",
"521055",
"521056",
"521057",
"521058",
"521059",
"521061",
"521060",
"521062",
"521063",
"521064",
"521065",
"521066",
"521067",
"521068",
"521069",
"521070",
"521071",
"521072",
"521073",
"2101",
"1031",
"521032",
"521036",
"521034",
"521035",
"521033",
"529501",
"529505",
"529502",
"529503",
"529506",
"529601",
"529701",
"529702",
"1",
"9100",
"8101",
"8102",
"8111",
"8112",
"8113",
"8114",
"8115",
"8116",
"8117",
"8118",
"8119",
"8120",
"8001",
"8002",
"8888",
"8889",
"200000",
"201001",
"201002",
"100004",
"522201",
"521217",
"521038",
"521037",
]
.to_vec()
}
pub fn get_recipe_table_dubai_16052025() -> Vec<&'static str> {
[
"Name",
"ProductCode",
"531001",
"531002",
"531004",
"531005",
"531006",
"531003",
"531007",
"531008",
"531009",
"531101",
"531102",
"531103",
"531104",
"531105",
"531106",
"531107",
"531108",
"531020",
"531021",
"531022",
"531023",
"531024",
"531025",
"531027",
"531028",
"531029",
"531030",
"531200",
"531201",
"531202",
"531203",
"531205",
"531206",
"531207",
"531208",
"531210",
"531211",
"531212",
"531213",
"531214",
"531215",
"531216",
"531301",
"531041",
"531042",
"531043",
"531044",
"531045",
"531046",
"531048",
"531047",
"531049",
"531050",
"531051",
"531052",
"531053",
"531054",
"531055",
"531056",
"531057",
"531058",
"531059",
"531061",
"531060",
"531062",
"531063",
"531064",
"531065",
"531066",
"531067",
"531068",
"531069",
"531070",
"531071",
"531072",
"531073",
"2101",
"1031",
"531032",
"531036",
"531034",
"531035",
"531033",
"539501",
"539505",
"539502",
"539503",
"539506",
"539601",
"539701",
"539702",
"1",
"9100",
"8101",
"8102",
"8111",
"8112",
"8113",
"8114",
"8115",
"8116",
"8117",
"8118",
"8119",
"8120",
"8001",
"8002",
"8888",
"8889",
"200000",
"201001",
"201002",
"100004",
"532201",
"531217",
"531038",
"531037",
]
.to_vec()
}
}

2
src/previews/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod csvf;
pub mod header;

View file

@ -0,0 +1,199 @@
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use serde_json::Value;
use crate::models::{self, recipe::Recipe01};
use std::collections::HashMap;
use std::{fs::File, io::Read};
pub const RECIPES_DIR: &str = "/Users/pkntd/Codes/mk4/cofffeemachineConfig/";
pub fn create_recipe_model_from_file(path: String) -> models::recipe::Recipe {
// println!("create_recipe_model_from_file: {}", path);
let mut file = File::open(path).unwrap();
// println!("check file: {}", file.metadata().unwrap().is_file());
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
models::recipe::Recipe::init(data)
}
pub fn create_recipe_path(country_path: &str, version: usize) -> String {
let mut path = String::from(RECIPES_DIR);
path.push_str(country_path);
path.push_str("/coffeethai02_");
// version
path.push_str(&version.to_string());
if country_path != "tha" {
path.push('_');
path.push_str(country_path);
}
path.push_str(".json");
path
}
pub fn read_all_files_in_directory(directory_path: &str) -> Vec<String> {
let mut files = Vec::new();
let dir = std::fs::read_dir(directory_path).unwrap();
for entry in dir {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() {
files.push(path.to_string_lossy().into_owned());
}
}
files
}
pub fn filter_files_by_pattern(files: Vec<String>, pattern: &str) -> Vec<String> {
files
.into_iter()
.filter(|file| file.contains(pattern))
.collect()
}
pub fn get_pd_prefix(country: &str) -> String {
match country {
"mys" => "12".to_string(),
"sgp" => "52".to_string(),
"aus" => "51".to_string(),
"tha" => "12".to_string(),
"hkg" => "54".to_string(),
"dubai" => "53".to_string(),
"uae" => "53".to_string(),
_ => "12".to_string(),
}
}
pub fn get_mat_prefix(country: &str) -> String {
match country {
"sgp" => "52".to_string(),
"aus" => "51".to_string(),
"hkg" => "54".to_string(),
"dubai" => "53".to_string(),
"uae" => "53".to_string(),
_ => "".to_string(),
}
}
pub fn check_allowed_change_mat_prefix(mat_id: Value) -> bool {
let mat_id_i = mat_id.as_i64().unwrap();
let exception_list = [
2101, 1031, 1, 9100, 8101, 8102, 8111, 8112, 8113, 8114, 8115, 8116, 8117, 8118, 8119,
8120, 8001, 8002, 8888, 8889, 200000, 201001, 201002, 100004, 0,
];
!exception_list.contains(&mat_id_i)
}
pub fn change_prefix_by_country(
country: &str,
mut recipes: Vec<Option<Recipe01>>,
) -> Vec<Recipe01> {
let mut result = Vec::new();
// get prefix first
let prefix = get_pd_prefix(country);
let mat_prefix = get_mat_prefix(country);
// run
let updated = recipes
.par_iter_mut()
.update(|x| {
if let Some(x) = x {
// change productCode
let mut new_product_code = x.productCode.clone();
new_product_code =
format!("{}-{}", prefix, new_product_code.split_once('-').unwrap().1);
x.productCode = new_product_code;
// run through recipes
let mut new_recipes_format = Vec::new();
for ele in x.clone().recipes {
let mut eled = ele.clone();
if check_allowed_change_mat_prefix(eled.clone().materialPathId) {
let new_f = format!("{}{}", mat_prefix, eled.clone().materialPathId);
eled.materialPathId = Value::Number(new_f.parse::<i64>().unwrap().into());
}
new_recipes_format.push(eled);
}
x.recipes = new_recipes_format;
if let Some(s) = x.clone().SubMenu {
//
let cls = s.clone();
for mut sl in cls.clone() {
let mut new_sl_pd = sl.productCode.clone();
new_sl_pd = format!("{}-{}", prefix, new_sl_pd.split_once('-').unwrap().1);
sl.productCode = new_sl_pd;
let mut new_recipes_sub = Vec::new();
for ele in sl.recipes {
let mut eled = ele.clone();
if check_allowed_change_mat_prefix(eled.clone().materialPathId) {
let new_f =
format!("{}{}", mat_prefix, eled.clone().materialPathId);
eled.materialPathId =
Value::Number(new_f.parse::<i64>().unwrap().into());
}
new_recipes_sub.push(eled);
}
sl.recipes = new_recipes_sub;
}
x.SubMenu = Some(cls.clone());
}
}
})
.filter(|x| x.is_some())
.map(|x| x.clone().unwrap())
.collect_vec_list();
// check by update
for x in updated.iter() {
result.extend_from_slice(x);
}
result
}
pub fn valid_country_name() -> Vec<&'static str> {
vec!["mys", "sgp", "aus", "tha", "hkg", "dubai", "uae"]
}
pub fn grep_latest_versions(dir_path: &str) -> Result<HashMap<String, usize>, std::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<_>, std::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)
}

View file

@ -0,0 +1,592 @@
use std::str::FromStr;
use log::{debug, info};
use serde_json::{Number, Value};
use super::common::*;
use crate::{
models::recipe::{self, PartialRecipe},
previews::{csvf::*, header::Headers},
};
pub fn import_menu(main_version: usize, country_name: &str, into_version: usize) {
let latest_recipe_thai = create_recipe_model_from_file(create_recipe_path("tha", main_version));
// let mut latest_recipe_mys =
// create_recipe_model_from_file(create_recipe_path("mys", *mys_version.unwrap()));
let mut latest_recipe_sgp =
create_recipe_model_from_file(create_recipe_path(country_name, into_version));
let diff_list = latest_recipe_thai.list_diff_pd_ignore_country_code(&latest_recipe_sgp);
let mut menu_no_sugar_table = Table::new();
let insert_later_config = TableConfig {
allow_row_length_exceeds_header: false,
auto_fix_size: true,
auto_fix_size_strategy: AutoFixSizeStrategy::FillRowToMatchHeader {
default_value: "".to_string(),
},
};
let mut sg_price_table = menu_no_sugar_table.clone();
let mut recipe_table = menu_no_sugar_table.clone();
menu_no_sugar_table.set_config(insert_later_config.clone());
sg_price_table.set_config(insert_later_config.clone());
recipe_table.set_config(insert_later_config.clone());
menu_no_sugar_table.set_header(["Product Code", "Name"].to_vec());
sg_price_table.set_header(["SKU", "English Name", "Thai Name"].to_vec());
recipe_table.set_header(Headers::get_recipe_table_sgp_24022025());
let modified_recipes = change_prefix_by_country(
country_name,
latest_recipe_thai.search_multi_in_parallel(diff_list),
);
// also clone mat setting
// ** Will removed this
let from_tha_mat_settings = [
latest_recipe_thai.search_material_settings("2201".to_string()),
latest_recipe_thai.search_material_settings("1217".to_string()),
latest_recipe_thai.search_material_settings("1038".to_string()),
latest_recipe_thai.search_material_settings("1037".to_string()),
];
let mut formatted_mat_settings = Vec::new();
from_tha_mat_settings.iter().for_each(|mat_smth| {
let new_mat_smth = *mat_smth;
if let Some(x) = new_mat_smth {
let mut cl_x = x.clone();
let mut new_mat_id = cl_x.id.clone().as_i64().unwrap().to_string();
// new_product_code = format!("{}-{}", "sgp", new_product_code.split_once('-').unwrap().1);
new_mat_id = format!("{}{}", get_mat_prefix(country_name), new_mat_id);
cl_x.id = Value::Number(Number::from_str(&new_mat_id).unwrap()).clone();
// debug!("new formatted mat settings [SGP] -> {:?}", cl_x);
formatted_mat_settings.push(cl_x);
}
});
latest_recipe_sgp
.MaterialSetting
.extend(formatted_mat_settings.clone());
// debug!("modified recipes: {:#?}", modified_recipes);
// process: generate table
modified_recipes.iter().enumerate().for_each(|mr| {
menu_no_sugar_table.add_row(vec![
mr.1.productCode.clone(),
mr.1.name.clone().unwrap().to_string().replace("\n", "\\n"),
]);
sg_price_table.add_row(vec![
mr.1.productCode.clone(),
mr.1.otherName
.clone()
.unwrap()
.to_string()
.replace("\n", "\\n"),
mr.1.name.clone().unwrap().to_string().replace("\n", "\\n"),
]);
// Pre process for recipe table
// let mut missing_support_mats = Vec::new();
let binding = mr.1.clone();
let mat_list = binding
.recipes
.iter()
.filter(|r| r.isUse)
.map(|r| {
// debug!(
// "r->{:?}",
// r.materialPathId
// .clone()
// .as_number()
// .unwrap()
// .as_i64()
// .unwrap()
// .to_string()
// );
r.materialPathId
.as_number()
.unwrap()
.as_i64()
.unwrap()
.to_string()
})
.collect::<Vec<String>>();
let mut mapping_mats = Vec::new();
// let mut missing_header = Vec::new();
mat_list.iter().enumerate().for_each(|(idx, mat)| {
// if (!mat.parse::<i64>().unwrap().eq(&0i64)
// && !recipe_table
// .clone()
// .get_current_header()
// .contains(&mat.to_string()))
// {
// // missing_support_mats.push((idx, mat.to_string()));
// // add to header
// // recipe_table.add_header(mat);
// missing_header.push(mat);
// }
// skip zero
if !mat.eq("0") {
mapping_mats.push((idx, mat));
}
});
// if (!missing_header.is_empty()) {
// missing_header.iter().for_each(|mh| {
// if (!recipe_table
// .clone()
// .get_current_header()
// .contains(mh.clone()))
// {
// recipe_table.add_header(mh);
// }
// });
// }
let inter_name_empty = mr.1.otherName.clone().unwrap().is_empty();
let used_name = if inter_name_empty {
mr.1.name.clone().unwrap().to_string().replace("\n", "\\n")
} else {
mr.1.otherName
.clone()
.unwrap()
.to_string()
.replace("\n", "\\n")
};
let mut used_mats = vec![used_name, mr.1.productCode.clone()];
// add by using mat
// let mut reorder1 = Vec::new();
// let mut add_last = Vec::new();
// reorder by header
// recipe_table.clone().get_current_header().iter().for_each(|h| {
// let mut found = false;
// mapping_mats.iter().for_each(|(idx, mat)| {
// if (h.eq(mat)) {
// found = true;
// reorder1.push((idx, mat));
// }
// });
// });
// recipe
let mut recipe_brew_values_only = vec![0; recipe_table.clone().get_current_header().len()];
let recipe_list = mr.1.clone().recipes;
let mut add_more_to_blender = 0i64;
for repl in recipe_list.iter() {
let current_mat = repl.materialPathId.as_number().unwrap().as_i64().unwrap();
if repl.isUse {
recipe_table
.clone()
.get_current_header()
.iter()
.enumerate()
.for_each(|(header_idx, header)| {
if header_idx > 1 && header.eq(&current_mat.to_string()) {
// search mat settings for further info
let mat_setting =
latest_recipe_sgp.search_material_settings(current_mat.to_string());
if let Some(mat_s) = mat_setting {
match mat_s.clone().get_definition_type() {
recipe::MaterialType::Bean => {
recipe_brew_values_only[header_idx] +=
repl.powderGram.as_i64().unwrap();
// check if there is mix order
if repl
.MixOrder
.as_number()
.unwrap()
.as_i64()
.unwrap()
.eq(&0i64)
{
// add to blender index
add_more_to_blender = repl.waterYield.as_i64().unwrap();
}
}
recipe::MaterialType::Syrup => {
// debug
println!(
"SYRUP [{}]: {} --> {} = {:?}",
mr.1.productCode.clone(),
header_idx,
repl.materialPathId.clone(),
repl.syrupGram
);
recipe_brew_values_only[header_idx] +=
repl.syrupGram.as_i64().unwrap();
}
recipe::MaterialType::Soda => {
recipe_brew_values_only[header_idx] +=
repl.syrupGram.as_i64().unwrap();
}
recipe::MaterialType::Powder => {
recipe_brew_values_only[header_idx] +=
repl.powderGram.as_i64().unwrap();
}
recipe::MaterialType::Water => todo!(),
recipe::MaterialType::Ice => {
recipe_brew_values_only[header_idx] +=
repl.waterCold.as_i64().unwrap();
}
recipe::MaterialType::Cup => {
recipe_brew_values_only[header_idx] += 1;
}
recipe::MaterialType::Lid => {}
recipe::MaterialType::Straw => {}
recipe::MaterialType::Whipper => {
let water_yield = repl.waterYield.as_i64().unwrap();
let water_cold = repl.waterCold.as_i64().unwrap();
recipe_brew_values_only[header_idx] +=
water_yield + water_cold;
if add_more_to_blender > 0i64 {
recipe_brew_values_only[header_idx] +=
add_more_to_blender;
add_more_to_blender = 0i64;
}
}
recipe::MaterialType::Leaves => {}
recipe::MaterialType::Clean => {
recipe_brew_values_only[header_idx] +=
repl.waterYield.as_i64().unwrap();
}
recipe::MaterialType::CleanV2 => {
recipe_brew_values_only[header_idx] +=
repl.waterYield.as_i64().unwrap();
}
recipe::MaterialType::Unknown => {
recipe_brew_values_only[header_idx] += 0i64;
}
}
} else {
// suspect new mat or not found
debug!("mat error [NotFound/Unknown] ==> {}", repl.materialPathId);
}
}
});
}
}
for (vidx, val) in recipe_brew_values_only.iter().enumerate() {
// skip first 2
if vidx <= 1 {
continue;
}
if val > &0i64 {
used_mats.push(val.to_string());
} else {
used_mats.push(String::from(""));
}
}
// debug!("Used Materials: \n{:?}", used_mats);
recipe_table.add_row(used_mats);
});
// Exclusive only for SGP
if country_name.eq_ignore_ascii_case("sgp") {
menu_no_sugar_table.generate_save_file("./test_result/menu_no_sugar.csv");
// process: generate table for price
sg_price_table.generate_save_file("./test_result/sg_price.csv");
// process: generate table for recipe
recipe_table.generate_save_file("./test_result/sg_recipe.csv");
// final process
let mut pr = PartialRecipe::default();
pr.sync_new(modified_recipes, formatted_mat_settings.clone());
// export file for recipe json
pr.export("./test_result/grep_into_modified_new_recipe_for_sgp.json");
} else {
sg_price_table.generate_save_file("./test_result/o1_price.csv");
// process: generate table for recipe
recipe_table.generate_save_file("./test_result/o1_recipe.csv");
// final process
let mut pr = PartialRecipe::default();
pr.sync_new(modified_recipes, formatted_mat_settings.clone());
// export file for recipe json
pr.export("./test_result/grep_into_modified_new_recipe_for_o1.json");
}
// WIP other countries
}
pub fn generate_recipe_sheet_table(country_name: &str, into_version: usize) {
let mut latest_recipe =
create_recipe_model_from_file(create_recipe_path(country_name, into_version));
let mut recipe_table = Table::new();
let insert_later_config = TableConfig {
allow_row_length_exceeds_header: false,
auto_fix_size: true,
auto_fix_size_strategy: AutoFixSizeStrategy::FillRowToMatchHeader {
default_value: "".to_string(),
},
};
recipe_table.set_config(insert_later_config);
match country_name {
"sgp" => {
info!("SGP last update [24.02.2025]");
recipe_table.set_header(Headers::get_recipe_table_sgp_24022025());
}
"dubai" => {
info!("UAE last update [16.05.2025]");
recipe_table.set_header(Headers::get_recipe_table_dubai_16052025());
}
_ => {}
}
let mut material_names = Vec::new();
for (idx, header) in recipe_table.clone().get_current_header().iter().enumerate() {
if idx <= 1 {
material_names.push("".to_string());
} else {
// do search from recipe first
let searched_result = latest_recipe.search_material_settings(header.to_string());
// material_names.push(header.to_string());
match searched_result {
Some(sr) => {
if sr.clone().materialOtherName.is_some() {
material_names.push(format!(
"{} ({})",
sr.clone().materialOtherName.unwrap(),
header
));
} else {
material_names.push(format!(
"{} ({})",
sr.clone().materialName.unwrap(),
header
));
}
}
None => {
material_names.push(header.to_string());
}
}
}
}
latest_recipe.clone().Recipe01.iter_mut().for_each(|rpl| {
let binding = rpl.clone();
let mat_list = binding
.recipes
.iter()
.filter(|r| r.isUse)
.map(|r| {
r.materialPathId
.as_number()
.unwrap()
.as_i64()
.unwrap()
.to_string()
})
.collect::<Vec<String>>();
let mut mapping_materials = Vec::new();
mat_list.iter().enumerate().for_each(|(idx, mat)| {
if !mat.eq("0") {
mapping_materials.push((idx, mat));
}
});
let inter_name_empty = rpl.otherName.clone().unwrap().is_empty();
let used_name = if inter_name_empty {
rpl.name.clone().unwrap().to_string().replace("\n", "\\n")
} else {
rpl.otherName
.clone()
.unwrap()
.to_string()
.replace("\n", "\\n")
};
let mut used_materials = vec![used_name, rpl.productCode.clone()];
let mut recipe_brewing_values_only =
vec![0; recipe_table.clone().get_current_header().len()];
let recipe_list = rpl.clone().recipes;
let mut add_more_to_blender = 0i64;
for repl in recipe_list.iter() {
let current_material = repl.materialPathId.as_number().unwrap().as_i64().unwrap();
if repl.isUse {
recipe_table
.clone()
.get_current_header()
.iter()
.enumerate()
.for_each(|(header_idx, header)| {
if header_idx > 1 && header.eq(&current_material.to_string()) {
// search mat settings for further info
if rpl.productCode.clone().ends_with("0090") {
debug!(
"MAT [{}]: {} --> {} = pow:{:?},sy:{:?}",
rpl.productCode.clone(),
header_idx,
repl.materialPathId.clone(),
repl.powderGram,
repl.syrupGram
);
}
let mat_setting = latest_recipe
.search_material_settings(current_material.to_string());
if let Some(mat_s) = mat_setting {
match mat_s.clone().get_definition_type() {
recipe::MaterialType::Bean => {
recipe_brewing_values_only[header_idx] +=
repl.powderGram.as_i64().unwrap();
// check if there is mix order
if repl
.MixOrder
.as_number()
.unwrap()
.as_i64()
.unwrap()
.eq(&0i64)
{
// add to blender index
add_more_to_blender = repl.waterYield.as_i64().unwrap();
}
}
recipe::MaterialType::Syrup => {
// debug
if rpl.productCode.clone().ends_with("0090") {
println!(
"SYRUP [{}]: {} --> {} = {:?}",
rpl.productCode.clone(),
header_idx,
repl.materialPathId.clone(),
repl.syrupGram
);
}
recipe_brewing_values_only[header_idx] +=
repl.syrupGram.as_i64().unwrap();
}
recipe::MaterialType::Soda => {
println!(
"SODA [{}]: {} --> {} = {:?}",
rpl.productCode.clone(),
header_idx,
repl.materialPathId.clone(),
repl.syrupGram
);
recipe_brewing_values_only[header_idx] +=
repl.syrupGram.as_i64().unwrap();
}
recipe::MaterialType::Powder => {
recipe_brewing_values_only[header_idx] +=
repl.powderGram.as_i64().unwrap();
}
recipe::MaterialType::Water => todo!(),
recipe::MaterialType::Ice => {
recipe_brewing_values_only[header_idx] +=
repl.waterCold.as_i64().unwrap();
}
recipe::MaterialType::Cup => {
recipe_brewing_values_only[header_idx] += 1;
}
recipe::MaterialType::Lid => {}
recipe::MaterialType::Straw => {}
recipe::MaterialType::Whipper => {
let water_yield = repl.waterYield.as_i64().unwrap();
let water_cold = repl.waterCold.as_i64().unwrap();
recipe_brewing_values_only[header_idx] +=
water_yield + water_cold;
if add_more_to_blender > 0i64 {
recipe_brewing_values_only[header_idx] +=
add_more_to_blender;
add_more_to_blender = 0i64;
}
}
recipe::MaterialType::Leaves => {}
recipe::MaterialType::Clean => {
recipe_brewing_values_only[header_idx] +=
repl.waterYield.as_i64().unwrap();
}
recipe::MaterialType::CleanV2 => {
recipe_brewing_values_only[header_idx] +=
repl.waterYield.as_i64().unwrap();
}
recipe::MaterialType::Unknown => {
if rpl.productCode.clone().ends_with("0090") {
println!(
"UNK [{}]: {} --> {} = {:?}",
rpl.productCode.clone(),
header_idx,
repl.materialPathId.clone(),
repl.syrupGram
);
}
recipe_brewing_values_only[header_idx] += 0i64;
}
}
} else {
// suspect new mat or not found
debug!("mat error [NotFound/Unknown] ==> {}", repl.materialPathId);
}
}
});
}
}
for (vidx, val) in recipe_brewing_values_only.iter().enumerate() {
if vidx <= 1 {
continue;
}
if val > &0i64 {
used_materials.push(val.to_string());
} else {
used_materials.push(String::from(""));
}
}
recipe_table.add_row(used_materials);
});
// generate row description
recipe_table.add_column_desciption(0, material_names);
recipe_table.generate_save_file("./test_result/recipe.csv");
}

View file

@ -0,0 +1,122 @@
use crate::models::recipe::{Recipe, Recipe01};
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()
}
}

View file

@ -0,0 +1,159 @@
use std::{
fmt,
fs::File,
io::{self, Read},
};
use super::list_fields;
use crate::models::{
merge_model::{MergeResult, MergeStatus},
recipe::{CommonRecipeTrait, Recipe, Recipe01Trait},
};
// compare between two recipe, and wait for user input to continue compare or quit
// return status
pub fn precise_merge(master: &mut Recipe, dev: &mut Recipe) -> MergeResult {
let mut merge_status = MergeResult::new();
if master.Timestamp != dev.Timestamp {
println!(
"\n\nmaster.Timestamp => {:?} but dev.Timestamp => {:?}",
master.Timestamp, dev.Timestamp
);
// do change if need
merge_status.accept(dev.Timestamp.clone());
}
let machine_setting_diff = master.MachineSetting.compare(&dev.MachineSetting);
println!("machine_setting_diff => {:?}", machine_setting_diff);
let product_codes = dev.list_menu_product_code();
let mut user_input = String::new();
println!("** Recip01 ** Sensitive data, enter 'm' (manual) or 'a' (auto) for safe comparing");
match io::stdin().read_line(&mut user_input) {
Ok(_m) => {}
Err(e) => {
println!("{:?}", e);
}
}
user_input = user_input.trim_end().to_string();
println!(
"mode: {}, valid: {}",
user_input,
user_input.eq_ignore_ascii_case("m")
);
for product_code in product_codes {
println!("** {} **", product_code);
let menu_master = master.clone();
let menu1 = menu_master.search_pd(product_code.clone().to_owned());
let menu_dev = dev.clone();
let menu2 = menu_dev.search_pd(product_code.clone().to_owned());
// exist in both
if menu1.is_some() && menu2.is_some() {
let menu_diff = menu1.unwrap().compare(menu2.unwrap());
if !menu_diff.is_empty()
&& menu1.unwrap().clone().is_replacable(
menu2.unwrap().to_owned(),
user_input.eq_ignore_ascii_case("m"),
)
{
master.update_menu(dev, product_code.clone().to_owned(), None);
println!("updated menu => {:?}\t\t{:?}", product_code, menu_diff);
merge_status.change(product_code.to_owned());
}
// check content of diff
} else if menu1.is_none() && menu2.is_some() {
// new menu
let new_menu = menu2.unwrap();
if user_input.contains('m') {
println!(
"found new menu from dev => {}, add to master ? (y,n)",
product_code
);
let mut user_input2 = String::new();
match io::stdin().read_line(&mut user_input2) {
Ok(_m) => {}
Err(e) => {
println!("{:?}", e);
}
}
if user_input.eq_ignore_ascii_case("y") {
master.Recipe01.push(new_menu.clone());
merge_status.add(product_code.to_owned());
}
} else {
master.Recipe01.push(new_menu.clone());
merge_status.add(product_code.to_owned());
}
}
}
let material_settings = dev.list_material_settings();
for material_setting in material_settings {
println!("material_setting => {}", material_setting);
let menu_master = master.clone();
let menu1 = menu_master.search_material_settings(material_setting.to_string());
let menu_dev = dev.clone();
let menu2 = menu_dev.search_material_settings(material_setting.to_string());
// exist in both
if menu1.is_some() && menu2.is_some() {
let menu_diff = menu1.unwrap().compare(menu2.unwrap());
if !menu_diff.is_empty() {
master.update_material_settings(dev, material_setting.to_string());
println!(
"updated material => {:?}\t\t{:?}",
material_setting, menu_diff
);
merge_status.change(material_setting.to_string());
}
// check content of diff
} else if menu1.is_none() && menu2.is_some() {
// new material
let new_material = menu2.unwrap();
if user_input.contains('m') {
println!(
"found new material from dev => {}, add to master ? (y,n)",
material_setting
);
let mut user_input2 = String::new();
match io::stdin().read_line(&mut user_input2) {
Ok(_m) => {}
Err(e) => {
println!("{:?}", e);
}
}
if user_input.eq_ignore_ascii_case("y") {
master.MaterialSetting.push(new_material.clone());
merge_status.add(material_setting.to_string());
}
} else {
master.MaterialSetting.push(new_material.clone());
merge_status.add(material_setting.to_string());
}
}
// not exist in both
}
for topp_list in dev.Topping.ToppingList.clone() {}
merge_status.finalize()
}

View file

@ -0,0 +1,4 @@
pub mod translator;
pub mod common;
pub mod import;

View file

@ -0,0 +1,20 @@
use serde_json::Number;
use crate::models::recipe::Recipe;
pub fn matIdToName(source: &mut Recipe, material_path_id: &str) -> String {
let mut name = String::new();
source.MaterialSetting.iter().for_each(|r| {
if r.id.as_i64().is_some()
&& r.id.as_i64().unwrap() == material_path_id.parse::<i64>().unwrap()
{
// name = r.materialName;
if let Some(mat_name) = r.materialName.clone() {
name = mat_name;
}
}
});
name
}