Add PRODUCTS, PRODUCT, SEARCH PRODUCTS keywords for ERP integration
This commit is contained in:
parent
1c4cc2f986
commit
ee9341163f
3 changed files with 283 additions and 0 deletions
|
|
@ -26,6 +26,7 @@ pub mod face_api;
|
||||||
pub mod file_operations;
|
pub mod file_operations;
|
||||||
pub mod find;
|
pub mod find;
|
||||||
pub mod first;
|
pub mod first;
|
||||||
|
pub mod products;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod for_next;
|
pub mod for_next;
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
|
@ -125,6 +126,9 @@ pub fn get_all_keywords() -> Vec<String> {
|
||||||
"FIND".to_string(),
|
"FIND".to_string(),
|
||||||
"FIRST".to_string(),
|
"FIRST".to_string(),
|
||||||
"SEARCH".to_string(),
|
"SEARCH".to_string(),
|
||||||
|
"SEARCH PRODUCTS".to_string(),
|
||||||
|
"PRODUCTS".to_string(),
|
||||||
|
"PRODUCT".to_string(),
|
||||||
"AUTOCOMPLETE".to_string(),
|
"AUTOCOMPLETE".to_string(),
|
||||||
"GROUP BY".to_string(),
|
"GROUP BY".to_string(),
|
||||||
"INSERT".to_string(),
|
"INSERT".to_string(),
|
||||||
|
|
|
||||||
277
src/basic/keywords/products.rs
Normal file
277
src/basic/keywords/products.rs
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
use crate::bot::get_default_bot;
|
||||||
|
use crate::core::shared::schema::products;
|
||||||
|
use crate::shared::models::UserSession;
|
||||||
|
use crate::shared::state::AppState;
|
||||||
|
use crate::shared::utils;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::sql_types::{Integer, Text};
|
||||||
|
use log::{error, trace};
|
||||||
|
use rhai::{Dynamic, Engine};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(QueryableByName)]
|
||||||
|
struct JsonRow {
|
||||||
|
#[diesel(sql_type = Text)]
|
||||||
|
row_data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn products_keyword(state: &AppState, _user: UserSession, engine: &mut Engine) {
|
||||||
|
let connection = state.conn.clone();
|
||||||
|
|
||||||
|
engine.register_fn("PRODUCTS", {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move || -> Dynamic {
|
||||||
|
let mut binding = match conn.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("PRODUCTS db error: {e}");
|
||||||
|
return Dynamic::from(rhai::Array::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match get_all_products(&mut binding) {
|
||||||
|
Ok(products) => utils::json_value_to_dynamic(&products),
|
||||||
|
Err(e) => {
|
||||||
|
error!("PRODUCTS error: {e}");
|
||||||
|
Dynamic::from(rhai::Array::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("products", {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move || -> Dynamic {
|
||||||
|
let mut binding = match conn.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("products db error: {e}");
|
||||||
|
return Dynamic::from(rhai::Array::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match get_all_products(&mut binding) {
|
||||||
|
Ok(products) => utils::json_value_to_dynamic(&products),
|
||||||
|
Err(e) => {
|
||||||
|
error!("products error: {e}");
|
||||||
|
Dynamic::from(rhai::Array::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("PRODUCT", {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move |id: i64| -> Dynamic {
|
||||||
|
let mut binding = match conn.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("PRODUCT db error: {e}");
|
||||||
|
return Dynamic::UNIT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match get_product_by_id(&mut binding, id) {
|
||||||
|
Ok(Some(product)) => utils::json_value_to_dynamic(&product),
|
||||||
|
Ok(None) => Dynamic::UNIT,
|
||||||
|
Err(e) => {
|
||||||
|
error!("PRODUCT error: {e}");
|
||||||
|
Dynamic::UNIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("product", {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move |id: i64| -> Dynamic {
|
||||||
|
let mut binding = match conn.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("product db error: {e}");
|
||||||
|
return Dynamic::UNIT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match get_product_by_id(&mut binding, id) {
|
||||||
|
Ok(Some(product)) => utils::json_value_to_dynamic(&product),
|
||||||
|
Ok(None) => Dynamic::UNIT,
|
||||||
|
Err(e) => {
|
||||||
|
error!("product error: {e}");
|
||||||
|
Dynamic::UNIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_custom_syntax(
|
||||||
|
["SEARCH", "PRODUCTS", "$expr$", ",", "$expr$"],
|
||||||
|
false,
|
||||||
|
{
|
||||||
|
let conn = connection.clone();
|
||||||
|
move |context, inputs| {
|
||||||
|
let query = context.eval_expression_tree(&inputs[0])?;
|
||||||
|
let limit = context.eval_expression_tree(&inputs[1])?;
|
||||||
|
|
||||||
|
let mut binding = conn.get().map_err(|e| format!("DB error: {e}"))?;
|
||||||
|
let query_str = query.to_string();
|
||||||
|
let limit_val = limit.as_int().unwrap_or(10) as i32;
|
||||||
|
|
||||||
|
let result = search_products(&mut binding, &query_str, limit_val)
|
||||||
|
.map_err(|e| format!("Search error: {e}"))?;
|
||||||
|
|
||||||
|
Ok(utils::json_value_to_dynamic(&result))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("valid syntax");
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_custom_syntax(["SEARCH", "PRODUCTS", "$expr$"], false, {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move |context, inputs| {
|
||||||
|
let query = context.eval_expression_tree(&inputs[0])?;
|
||||||
|
|
||||||
|
let mut binding = conn.get().map_err(|e| format!("DB error: {e}"))?;
|
||||||
|
let query_str = query.to_string();
|
||||||
|
|
||||||
|
let result = search_products(&mut binding, &query_str, 10)
|
||||||
|
.map_err(|e| format!("Search error: {e}"))?;
|
||||||
|
|
||||||
|
Ok(utils::json_value_to_dynamic(&result))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("valid syntax");
|
||||||
|
|
||||||
|
engine.register_fn("SEARCH_PRODUCTS", {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move |query: String, limit: i64| -> Dynamic {
|
||||||
|
let mut binding = match conn.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("SEARCH_PRODUCTS db error: {e}");
|
||||||
|
return Dynamic::from(rhai::Array::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match search_products(&mut binding, &query, limit as i32) {
|
||||||
|
Ok(products) => utils::json_value_to_dynamic(&products),
|
||||||
|
Err(e) => {
|
||||||
|
error!("SEARCH_PRODUCTS error: {e}");
|
||||||
|
Dynamic::from(rhai::Array::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("search_products", {
|
||||||
|
let conn = connection.clone();
|
||||||
|
move |query: String, limit: i64| -> Dynamic {
|
||||||
|
let mut binding = match conn.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("search_products db error: {e}");
|
||||||
|
return Dynamic::from(rhai::Array::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match search_products(&mut binding, &query, limit as i32) {
|
||||||
|
Ok(products) => utils::json_value_to_dynamic(&products),
|
||||||
|
Err(e) => {
|
||||||
|
error!("search_products error: {e}");
|
||||||
|
Dynamic::from(rhai::Array::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all_products(conn: &mut diesel::PgConnection) -> Result<Value, String> {
|
||||||
|
trace!("get_all_products");
|
||||||
|
|
||||||
|
let (bot_id, _) = get_default_bot(conn);
|
||||||
|
|
||||||
|
let query = r#"
|
||||||
|
SELECT row_to_json(p)::text as row_data
|
||||||
|
FROM products p
|
||||||
|
WHERE p.bot_id = $1 AND p.is_active = true
|
||||||
|
ORDER BY p.name
|
||||||
|
LIMIT 100
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let results: Vec<JsonRow> = diesel::sql_query(query)
|
||||||
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let products: Vec<Value> = results
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|row| serde_json::from_str(&row.row_data).ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(json!(products))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_product_by_id(conn: &mut diesel::PgConnection, id: i64) -> Result<Option<Value>, String> {
|
||||||
|
trace!("get_product_by_id: {id}");
|
||||||
|
|
||||||
|
let query = r#"
|
||||||
|
SELECT row_to_json(p)::text as row_data
|
||||||
|
FROM products p
|
||||||
|
WHERE p.id = $1
|
||||||
|
LIMIT 1
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let uuid = uuid::Uuid::from_u64_pair(0, id as u64);
|
||||||
|
|
||||||
|
let results: Vec<JsonRow> = diesel::sql_query(query)
|
||||||
|
.bind::<diesel::sql_types::Uuid, _>(uuid)
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(results
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.and_then(|row| serde_json::from_str(&row.row_data).ok()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_products(conn: &mut diesel::PgConnection, query: &str, limit: i32) -> Result<Value, String> {
|
||||||
|
trace!("search_products: query={query}, limit={limit}");
|
||||||
|
|
||||||
|
let (bot_id, _) = get_default_bot(conn);
|
||||||
|
let safe_query = query.replace('\'', "''");
|
||||||
|
|
||||||
|
let sql = r#"
|
||||||
|
SELECT row_to_json(p)::text as row_data
|
||||||
|
FROM products p
|
||||||
|
WHERE p.bot_id = $1
|
||||||
|
AND p.is_active = true
|
||||||
|
AND (
|
||||||
|
p.name ILIKE '%' || $2 || '%'
|
||||||
|
OR p.description ILIKE '%' || $2 || '%'
|
||||||
|
OR p.sku ILIKE '%' || $2 || '%'
|
||||||
|
OR p.brand ILIKE '%' || $2 || '%'
|
||||||
|
OR p.category ILIKE '%' || $2 || '%'
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN p.name ILIKE $2 || '%' THEN 0 ELSE 1 END,
|
||||||
|
p.name
|
||||||
|
LIMIT $3
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let results: Vec<JsonRow> = diesel::sql_query(sql)
|
||||||
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||||
|
.bind::<Text, _>(&safe_query)
|
||||||
|
.bind::<Integer, _>(limit)
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let products: Vec<Value> = results
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|row| serde_json::from_str(&row.row_data).ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(json!(products))
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ use self::keywords::data_operations::register_data_operations;
|
||||||
use self::keywords::file_operations::register_file_operations;
|
use self::keywords::file_operations::register_file_operations;
|
||||||
use self::keywords::find::find_keyword;
|
use self::keywords::find::find_keyword;
|
||||||
use self::keywords::search::search_keyword;
|
use self::keywords::search::search_keyword;
|
||||||
|
use self::keywords::products::products_keyword;
|
||||||
use self::keywords::first::first_keyword;
|
use self::keywords::first::first_keyword;
|
||||||
use self::keywords::for_next::for_keyword;
|
use self::keywords::for_next::for_keyword;
|
||||||
use self::keywords::format::format_keyword;
|
use self::keywords::format::format_keyword;
|
||||||
|
|
@ -88,6 +89,7 @@ impl ScriptService {
|
||||||
create_site_keyword(&state, user.clone(), &mut engine);
|
create_site_keyword(&state, user.clone(), &mut engine);
|
||||||
find_keyword(&state, user.clone(), &mut engine);
|
find_keyword(&state, user.clone(), &mut engine);
|
||||||
search_keyword(&state, user.clone(), &mut engine);
|
search_keyword(&state, user.clone(), &mut engine);
|
||||||
|
products_keyword(&state, user.clone(), &mut engine);
|
||||||
for_keyword(&state, user.clone(), &mut engine);
|
for_keyword(&state, user.clone(), &mut engine);
|
||||||
let _ = register_use_kb_keyword(&mut engine, state.clone(), Arc::new(user.clone()));
|
let _ = register_use_kb_keyword(&mut engine, state.clone(), Arc::new(user.clone()));
|
||||||
let _ = register_clear_kb_keyword(&mut engine, state.clone(), Arc::new(user.clone()));
|
let _ = register_clear_kb_keyword(&mut engine, state.clone(), Arc::new(user.clone()));
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue