From e5208fead282a3eaf6cd5d683687e19684d4f564 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Tue, 30 Jan 2024 11:36:33 +1300 Subject: [PATCH 01/38] add GS1 to dgraph schema --- data-loader/data/v2/schema.graphql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data-loader/data/v2/schema.graphql b/data-loader/data/v2/schema.graphql index 6e53877fd..a80f4345e 100644 --- a/data-loader/data/v2/schema.graphql +++ b/data-loader/data/v2/schema.graphql @@ -17,6 +17,7 @@ type Entity { properties: [Property] @dgraph(pred: "properties") children: [Entity] @dgraph(pred: "children") parents: [Entity] @dgraph(pred: "~children") + gs1s: [GS1] @dgraph(pred: "gs1s") @hasInverse(field: entity) } type Property { @@ -67,3 +68,10 @@ type PropertyConfigurationItem { label: String @dgraph(pred: "label") url: String @dgraph(pred: "url") } + +type GS1 { + id: ID! + gtin: String! @id @dgraph(pred: "code") @search(by: [exact, trigram]) + manufacturer: String @dgraph(pred: "manufacturer") + entity: Entity @dgraph(pred: "entity") +} From 1fec9b813d49d27c6a763ef909f34a4ad13ec18d Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Tue, 30 Jan 2024 14:17:00 +1300 Subject: [PATCH 02/38] fix the detail redirect --- frontend/host/src/Site.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/host/src/Site.tsx b/frontend/host/src/Site.tsx index c7dc3b2a8..c120452a7 100644 --- a/frontend/host/src/Site.tsx +++ b/frontend/host/src/Site.tsx @@ -92,7 +92,7 @@ export const Site: FC = () => { /> } /> - } /> + } /> } /> From 0afd3419975ea2d74974e13943e6d06d466423ec Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Tue, 30 Jan 2024 14:30:33 +1300 Subject: [PATCH 03/38] gs1 dgraph layer --- backend/dgraph/src/gs1/delete_gs1.rs | 106 +++++++++++++++++++++++++++ backend/dgraph/src/gs1/gs1.rs | 39 ++++++++++ backend/dgraph/src/gs1/gs1s.rs | 22 ++++++ backend/dgraph/src/gs1/insert_gs1.rs | 100 +++++++++++++++++++++++++ backend/dgraph/src/gs1/mod.rs | 20 +++++ backend/dgraph/src/lib.rs | 2 + data-loader/data/v2/schema.graphql | 4 +- 7 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 backend/dgraph/src/gs1/delete_gs1.rs create mode 100644 backend/dgraph/src/gs1/gs1.rs create mode 100644 backend/dgraph/src/gs1/gs1s.rs create mode 100644 backend/dgraph/src/gs1/insert_gs1.rs create mode 100644 backend/dgraph/src/gs1/mod.rs diff --git a/backend/dgraph/src/gs1/delete_gs1.rs b/backend/dgraph/src/gs1/delete_gs1.rs new file mode 100644 index 000000000..6822beaee --- /dev/null +++ b/backend/dgraph/src/gs1/delete_gs1.rs @@ -0,0 +1,106 @@ +use gql_client::GraphQLError; +use serde::Serialize; + +use crate::{DeleteResponse, DeleteResponseData, DgraphClient}; + +#[derive(Serialize, Debug, Clone)] +struct DeleteVars { + gtin: String, +} + +pub async fn delete_gs1( + client: &DgraphClient, + gtin: String, +) -> Result { + let query = r#" +mutation DeleteGS1($gtin: String) { + data: deleteGS1(filter: {gtin: {eq: $gtin}}) { + numUids + } +}"#; + let variables = DeleteVars { gtin }; + + let result = client + .query_with_retry::(&query, variables) + .await?; + + match result { + Some(result) => { + return Ok(DeleteResponse { + numUids: result.data.numUids, + }) + } + None => return Ok(DeleteResponse { numUids: 0 }), + } +} + +#[cfg(test)] +#[cfg(feature = "dgraph-tests")] +mod tests { + use crate::{ + gs1::gs1::gs1_by_gtin, + insert_gs1::{insert_gs1, EntityCode, GS1Input}, + }; + use util::uuid::uuid; + + use super::*; + + #[tokio::test] + async fn test_delete_configuration_item() { + let client = DgraphClient::new("http://localhost:8080/graphql"); + + // Create a GS1Input instance + let gs1_input = GS1Input { + manufacturer: "test_manufacturer".to_string(), + gtin: uuid(), + entity: EntityCode { + code: "c7750265".to_string(), + }, + }; + + let result = insert_gs1(&client, gs1_input.clone(), true).await; + if result.is_err() { + println!( + "insert_gs1 err: {:#?} {:#?}", + result, + result.clone().unwrap_err().json() + ); + }; + + // GS1 exists + let result = gs1_by_gtin(&client, gs1_input.gtin.clone()).await; + + if result.is_err() { + println!( + "gs1_by_gtin err: {:#?} {:#?}", + result, + result.clone().unwrap_err().json() + ); + }; + + assert!(result.unwrap().is_some()); + + let result = delete_gs1(&client, gs1_input.gtin.clone()).await; + if result.is_err() { + println!( + "delete_gs1 err: {:#?} {:#?}", + result, + result.clone().unwrap_err().json() + ); + }; + assert!(result.is_ok()); + + // GS1 no longer exists + let result = gs1_by_gtin(&client, gs1_input.gtin.clone()).await; + + if result.is_err() { + println!( + "gs1_by_gtin err: {:#?} {:#?}", + result, + result.clone().unwrap_err().json() + ); + }; + + assert!(result.unwrap().is_none()); + } +} diff --git a/backend/dgraph/src/gs1/gs1.rs b/backend/dgraph/src/gs1/gs1.rs new file mode 100644 index 000000000..b0ee2027c --- /dev/null +++ b/backend/dgraph/src/gs1/gs1.rs @@ -0,0 +1,39 @@ +use gql_client::GraphQLError; +use serde::Serialize; + +use crate::{DgraphClient, GS1Data, GS1}; + +#[derive(Serialize, Debug, Clone)] +struct GS1Vars { + gtin: String, +} + +pub async fn gs1_by_gtin(client: &DgraphClient, gtin: String) -> Result, GraphQLError> { + let query = r#" +query gs1($gtin: String!) { + data: queryGS1(filter: {gtin: {eq: $gtin}}) { + gtin + manufacturer + entity { + description + name + code + } + } +} +"#; + let vars = GS1Vars { gtin }; + + let result = client + .gql + .query_with_vars::(query, vars) + .await?; + + match result { + Some(result) => match result.data.first() { + Some(gs1) => Ok(Some(gs1.clone())), + None => Ok(None), + }, + None => Ok(None), + } +} diff --git a/backend/dgraph/src/gs1/gs1s.rs b/backend/dgraph/src/gs1/gs1s.rs new file mode 100644 index 000000000..6e06ebfa6 --- /dev/null +++ b/backend/dgraph/src/gs1/gs1s.rs @@ -0,0 +1,22 @@ +use gql_client::GraphQLError; + +use crate::{DgraphClient, GS1Data}; + +pub async fn gs1s(client: &DgraphClient) -> Result, GraphQLError> { + let query = r#" +query gs1s { + data: queryGS1 { + manufacturer + gtin + entity { + description + name + code + } + } +} +"#; + let data = client.gql.query::(query).await?; + + Ok(data) +} diff --git a/backend/dgraph/src/gs1/insert_gs1.rs b/backend/dgraph/src/gs1/insert_gs1.rs new file mode 100644 index 000000000..7ffec8baa --- /dev/null +++ b/backend/dgraph/src/gs1/insert_gs1.rs @@ -0,0 +1,100 @@ +use gql_client::GraphQLError; +use serde::Serialize; + +use crate::{DgraphClient, UpsertResponse, UpsertResponseData}; + +#[derive(Serialize, Debug, Clone)] +pub struct EntityCode { + pub code: String, +} + +#[derive(Serialize, Debug, Clone)] +pub struct GS1Input { + pub manufacturer: String, + pub gtin: String, + pub entity: EntityCode, +} + +#[derive(Serialize, Debug, Clone)] +struct UpsertVars { + input: GS1Input, + upsert: bool, +} + +pub async fn insert_gs1( + client: &DgraphClient, + gs1: GS1Input, + upsert: bool, +) -> Result { + let query = r#" +mutation AddGS1($input: [AddGS1Input!]!, $upsert: Boolean = false) { + data: addGS1(input: $input, upsert: $upsert) { + numUids + } +}"#; + let variables = UpsertVars { input: gs1, upsert }; + + let result = client + .query_with_retry::(&query, variables) + .await?; + + match result { + Some(result) => { + return Ok(UpsertResponse { + numUids: result.data.numUids, + }) + } + None => return Ok(UpsertResponse { numUids: 0 }), + } +} + +#[cfg(test)] +#[cfg(feature = "dgraph-tests")] +mod tests { + use crate::gs1::delete_gs1::delete_gs1; + use crate::gs1::gs1::gs1_by_gtin; + use util::uuid::uuid; + + use super::*; + + #[tokio::test] + async fn test_insert_gs1() { + // Create a DgraphClient instance + let client = DgraphClient::new("http://localhost:8080/graphql"); + + // Create a GS1Input instance + let gs1_input = GS1Input { + manufacturer: "test_manufacturer".to_string(), + gtin: uuid(), + entity: EntityCode { + code: "c7750265".to_string(), + }, + }; + + let result = insert_gs1(&client, gs1_input.clone(), true).await; + if result.is_err() { + println!( + "insert_gs1 err: {:#?} {:#?}", + result, + result.clone().unwrap_err().json() + ); + }; + + // Check if the new record can be found by querying for the gtin + let result = gs1_by_gtin(&client, gs1_input.gtin.clone()).await; + + if result.is_err() { + println!( + "gs1_by_gtin err: {:#?} {:#?}", + result, + result.clone().unwrap_err().json() + ); + }; + + let data = result.unwrap().unwrap(); + assert_eq!(data.manufacturer, gs1_input.manufacturer); + + // Delete the record + let _result = delete_gs1(&client, gs1_input.gtin.clone()).await; + } +} diff --git a/backend/dgraph/src/gs1/mod.rs b/backend/dgraph/src/gs1/mod.rs new file mode 100644 index 000000000..1f70b0f3f --- /dev/null +++ b/backend/dgraph/src/gs1/mod.rs @@ -0,0 +1,20 @@ +use serde::Deserialize; + +use crate::Entity; + +pub mod delete_gs1; +pub mod gs1; +pub mod gs1s; +pub mod insert_gs1; + +#[derive(Deserialize, Debug, Clone)] +pub struct GS1 { + pub gtin: String, + pub manufacturer: String, + pub entity: Entity, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct GS1Data { + pub data: Vec, +} diff --git a/backend/dgraph/src/lib.rs b/backend/dgraph/src/lib.rs index a4bbe98a5..2bc2febaf 100644 --- a/backend/dgraph/src/lib.rs +++ b/backend/dgraph/src/lib.rs @@ -26,6 +26,8 @@ pub mod upsert_entity; pub use upsert_entity::*; pub mod link_codes; pub use link_codes::*; +pub mod gs1; +pub use gs1::*; pub use gql_client::GraphQLError; diff --git a/data-loader/data/v2/schema.graphql b/data-loader/data/v2/schema.graphql index a80f4345e..8662268df 100644 --- a/data-loader/data/v2/schema.graphql +++ b/data-loader/data/v2/schema.graphql @@ -72,6 +72,6 @@ type PropertyConfigurationItem { type GS1 { id: ID! gtin: String! @id @dgraph(pred: "code") @search(by: [exact, trigram]) - manufacturer: String @dgraph(pred: "manufacturer") - entity: Entity @dgraph(pred: "entity") + manufacturer: String! @dgraph(pred: "manufacturer") + entity: Entity! @dgraph(pred: "entity") } From 4468b61627ce486f8edcf97b8cd0a74aa055c8ad Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Tue, 30 Jan 2024 15:42:42 +1300 Subject: [PATCH 04/38] gs1 service layer --- backend/dgraph/src/gs1/insert_gs1.rs | 2 +- backend/graphql/types/src/types/log.rs | 6 + .../repository/src/db_diesel/audit_log_row.rs | 2 + backend/service/src/gs1/delete.rs | 49 ++++++ backend/service/src/gs1/mod.rs | 125 +++++++++++++++ backend/service/src/gs1/tests/delete.rs | 79 +++++++++ backend/service/src/gs1/tests/mod.rs | 5 + backend/service/src/gs1/tests/upsert.rs | 151 ++++++++++++++++++ backend/service/src/gs1/upsert.rs | 115 +++++++++++++ backend/service/src/lib.rs | 1 + backend/service/src/service_provider.rs | 3 + 11 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 backend/service/src/gs1/delete.rs create mode 100644 backend/service/src/gs1/mod.rs create mode 100644 backend/service/src/gs1/tests/delete.rs create mode 100644 backend/service/src/gs1/tests/mod.rs create mode 100644 backend/service/src/gs1/tests/upsert.rs create mode 100644 backend/service/src/gs1/upsert.rs diff --git a/backend/dgraph/src/gs1/insert_gs1.rs b/backend/dgraph/src/gs1/insert_gs1.rs index 7ffec8baa..894ffc4d6 100644 --- a/backend/dgraph/src/gs1/insert_gs1.rs +++ b/backend/dgraph/src/gs1/insert_gs1.rs @@ -95,6 +95,6 @@ mod tests { assert_eq!(data.manufacturer, gs1_input.manufacturer); // Delete the record - let _result = delete_gs1(&client, gs1_input.gtin.clone()).await; + let _result = delete_gs1(&client, gs1_input.gtin.clone()).await.unwrap(); } } diff --git a/backend/graphql/types/src/types/log.rs b/backend/graphql/types/src/types/log.rs index 7bd8f7a1e..942a7c371 100644 --- a/backend/graphql/types/src/types/log.rs +++ b/backend/graphql/types/src/types/log.rs @@ -28,6 +28,8 @@ pub enum LogNodeType { UniversalCodeChangeApproved, UniversalCodeChangeRejected, UniversalCodeChangeRequested, + GS1Created, + GS1Deleted, ConfigurationItemCreated, ConfigurationItemDeleted, PropertyConfigurationItemUpserted, @@ -95,6 +97,8 @@ impl LogNodeType { LogType::PropertyConfigurationItemUpserted => { LogNodeType::PropertyConfigurationItemUpserted } + LogType::GS1Created => LogNodeType::GS1Created, + LogType::GS1Deleted => LogNodeType::GS1Deleted, } } @@ -116,6 +120,8 @@ impl LogNodeType { LogNodeType::PropertyConfigurationItemUpserted => { LogType::PropertyConfigurationItemUpserted } + LogNodeType::GS1Created => LogType::GS1Created, + LogNodeType::GS1Deleted => LogType::GS1Deleted, } } } diff --git a/backend/repository/src/db_diesel/audit_log_row.rs b/backend/repository/src/db_diesel/audit_log_row.rs index 7caa4d9d2..fa42ae739 100644 --- a/backend/repository/src/db_diesel/audit_log_row.rs +++ b/backend/repository/src/db_diesel/audit_log_row.rs @@ -28,6 +28,8 @@ pub enum LogType { UniversalCodeChangeRequested, UniversalCodeCreated, UniversalCodeUpdated, + GS1Created, + GS1Deleted, ConfigurationItemCreated, ConfigurationItemDeleted, PropertyConfigurationItemUpserted, diff --git a/backend/service/src/gs1/delete.rs b/backend/service/src/gs1/delete.rs new file mode 100644 index 000000000..07c122357 --- /dev/null +++ b/backend/service/src/gs1/delete.rs @@ -0,0 +1,49 @@ +use std::sync::Arc; + +use crate::{ + audit_log::audit_log_entry, + service_provider::{ServiceContext, ServiceProvider}, +}; +use chrono::Utc; +use dgraph::{delete_gs1::delete_gs1 as dgraph_delete_gs1, gs1::gs1::gs1_by_gtin}; +use repository::LogType; + +use super::ModifyGS1Error; + +pub async fn delete_gs1( + sp: Arc, + user_id: String, + client: dgraph::DgraphClient, + gtin: String, +) -> Result { + validate(&client, gtin.clone()).await?; + + let result = dgraph_delete_gs1(&client, gtin.clone()).await?; + + // Audit logging + let service_context = ServiceContext::with_user(sp.clone(), user_id)?; + audit_log_entry( + &service_context, + LogType::GS1Deleted, + Some(gtin), + Utc::now().naive_utc(), + )?; + + Ok(result.numUids) +} + +async fn validate(client: &dgraph::DgraphClient, gtin: String) -> Result<(), ModifyGS1Error> { + // Check that the gtin does exist + let result = gs1_by_gtin(client, gtin.clone()).await.map_err(|e| { + ModifyGS1Error::InternalError(format!("Failed to get gs1 by gtin: {}", e.message())) + })?; + + match result { + Some(_) => {} + None => { + return Err(ModifyGS1Error::GS1DoesNotExist); + } + } + + Ok(()) +} diff --git a/backend/service/src/gs1/mod.rs b/backend/service/src/gs1/mod.rs new file mode 100644 index 000000000..11d04bdfc --- /dev/null +++ b/backend/service/src/gs1/mod.rs @@ -0,0 +1,125 @@ +use std::{ + fmt::{Display, Formatter}, + sync::Arc, +}; + +use dgraph::{gs1::gs1::gs1_by_gtin, gs1s::gs1s, DgraphClient, GraphQLError, GS1}; +use repository::RepositoryError; +use util::usize_to_u32; + +use crate::{service_provider::ServiceProvider, settings::Settings}; + +#[derive(Debug)] +pub enum ModifyGS1Error { + GS1DoesNotExist, + GS1AlreadyExists, + UniversalCodeDoesNotExist, + InternalError(String), + DatabaseError(RepositoryError), + DgraphError(GraphQLError), +} + +impl From for ModifyGS1Error { + fn from(error: RepositoryError) -> Self { + ModifyGS1Error::DatabaseError(error) + } +} + +impl From for ModifyGS1Error { + fn from(error: GraphQLError) -> Self { + ModifyGS1Error::DgraphError(error) + } +} + +mod tests; + +pub mod delete; +pub mod upsert; + +pub struct GS1Service { + client: DgraphClient, +} + +#[derive(Debug)] +pub enum GS1ServiceError { + InternalError(String), + BadUserInput(String), +} + +impl Display for GS1ServiceError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + GS1ServiceError::InternalError(details) => { + write!(f, "Internal error: {}", details) + } + GS1ServiceError::BadUserInput(details) => { + write!(f, "Bad user input: {}", details) + } + } + } +} + +pub struct GS1Collection { + pub data: Vec, + pub total_length: u32, +} + +impl GS1Service { + pub fn new(settings: Settings) -> Self { + let url = format!( + "{}:{}/graphql", + settings.dgraph.host.clone(), + settings.dgraph.port + ); + + GS1Service { + client: DgraphClient::new(&url), + } + } + + pub async fn gs1s(&self) -> Result { + let result = gs1s(&self.client) + .await + .map_err(|e| GS1ServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? + + match result { + Some(data) => Ok(GS1Collection { + total_length: usize_to_u32(data.data.len()), + data: data.data, + }), + None => Ok(GS1Collection { + data: vec![], + total_length: 0, + }), + } + } + + pub async fn gs1_by_gtin(&self, gtin: String) -> Result, GS1ServiceError> { + let result = gs1_by_gtin(&self.client, gtin) + .await + .map_err(|e| GS1ServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? + + match result { + Some(result) => Ok(Some(result)), + None => Ok(None), + } + } + + pub async fn add_gs1( + &self, + sp: Arc, + user_id: String, + item: upsert::AddGS1, + ) -> Result { + upsert::add_gs1(sp, user_id, self.client.clone(), item).await + } + + pub async fn delete_gs1( + &self, + sp: Arc, + user_id: String, + gtin: String, + ) -> Result { + delete::delete_gs1(sp, user_id, self.client.clone(), gtin).await + } +} diff --git a/backend/service/src/gs1/tests/delete.rs b/backend/service/src/gs1/tests/delete.rs new file mode 100644 index 000000000..3421835e6 --- /dev/null +++ b/backend/service/src/gs1/tests/delete.rs @@ -0,0 +1,79 @@ +#[cfg(test)] +#[cfg(feature = "dgraph-tests")] +mod test { + use repository::{mock::MockDataInserts, test_db::setup_all}; + use std::sync::Arc; + use util::uuid::uuid; + + use crate::gs1::upsert::AddGS1; + use crate::service_provider::ServiceContext; + use crate::service_provider::ServiceProvider; + + use crate::test_utils::get_test_settings; + + #[actix_rt::test] + async fn delete_gs1_success() { + let (_, _, connection_manager, _) = + setup_all("delete_gs1_success", MockDataInserts::none()).await; + + let service_provider = Arc::new(ServiceProvider::new( + connection_manager, + get_test_settings(""), + )); + let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); + let service = &context.service_provider.gs1_service; + + let new_gtin = uuid(); + let input = AddGS1 { + gtin: new_gtin.clone(), + manufacturer: "test_manufacturer".to_string(), + entity_code: "c7750265".to_string(), + }; + + let result = service + .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .await + .unwrap(); + assert_eq!(result.gtin, new_gtin); + + // Delete the newly created gs1 + let _result = service + .delete_gs1( + service_provider.clone(), + context.user_id.clone(), + new_gtin.clone(), + ) + .await + .unwrap(); + + // Check the gs1 no longer exists + let result = service.gs1_by_gtin(new_gtin.clone()).await.unwrap(); + assert!(result.is_none()); + } + + #[actix_rt::test] + async fn delete_gs1_gtin_doesnt_exist() { + let (_, _, connection_manager, _) = + setup_all("delete_gs1_gtin_doesnt_exist", MockDataInserts::none()).await; + + let service_provider = Arc::new(ServiceProvider::new( + connection_manager, + get_test_settings(""), + )); + let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); + let service = &context.service_provider.gs1_service; + + let some_gtin = uuid(); + + // Try delete non-existent gs1 + let result = service + .delete_gs1( + service_provider.clone(), + context.user_id.clone(), + some_gtin.clone(), + ) + .await; + + assert!(result.is_err()); + } +} diff --git a/backend/service/src/gs1/tests/mod.rs b/backend/service/src/gs1/tests/mod.rs new file mode 100644 index 000000000..6e4171442 --- /dev/null +++ b/backend/service/src/gs1/tests/mod.rs @@ -0,0 +1,5 @@ +#[cfg(test)] +mod upsert; + +#[cfg(test)] +mod delete; diff --git a/backend/service/src/gs1/tests/upsert.rs b/backend/service/src/gs1/tests/upsert.rs new file mode 100644 index 000000000..0408411c2 --- /dev/null +++ b/backend/service/src/gs1/tests/upsert.rs @@ -0,0 +1,151 @@ +#[cfg(test)] +#[cfg(feature = "dgraph-tests")] +mod test { + use repository::{mock::MockDataInserts, test_db::setup_all}; + use std::sync::Arc; + use util::uuid::uuid; + + use crate::gs1::upsert::AddGS1; + use crate::service_provider::ServiceContext; + use crate::service_provider::ServiceProvider; + + use crate::test_utils::get_test_settings; + + #[actix_rt::test] + async fn add_gs1_success() { + let (_, _, connection_manager, _) = + setup_all("add_gs1_success", MockDataInserts::none()).await; + + let service_provider = Arc::new(ServiceProvider::new( + connection_manager, + get_test_settings(""), + )); + let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); + let service = &context.service_provider.gs1_service; + + let new_gtin = uuid(); + let input = AddGS1 { + gtin: new_gtin.clone(), + manufacturer: "test_manufacturer".to_string(), + entity_code: "c7750265".to_string(), + }; + + let result = service + .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .await + .unwrap(); + assert_eq!(result.gtin, new_gtin); + + // Delete the newly created gs1 + let _result = service + .delete_gs1( + service_provider.clone(), + context.user_id.clone(), + new_gtin.clone(), + ) + .await + .unwrap(); + } + + #[actix_rt::test] + async fn add_gs1_no_gtin() { + let (_, _, connection_manager, _) = + setup_all("add_gs1_no_gtin", MockDataInserts::none()).await; + + let service_provider = Arc::new(ServiceProvider::new( + connection_manager, + get_test_settings(""), + )); + let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); + let service = &context.service_provider.gs1_service; + + let input = AddGS1 { + gtin: "".to_string(), + manufacturer: "test_manufacturer".to_string(), + entity_code: "c7750265".to_string(), + }; + + let result = service + .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .await; + assert!(result.is_err()); + } + + #[actix_rt::test] + async fn add_gs1_gs1_already_exists() { + let (_, _, connection_manager, _) = + setup_all("add_gs1_gs1_already_exists", MockDataInserts::none()).await; + + let service_provider = Arc::new(ServiceProvider::new( + connection_manager, + get_test_settings(""), + )); + let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); + let service = &context.service_provider.gs1_service; + + // Add new GS1 + let new_gtin = uuid(); + let input = AddGS1 { + gtin: new_gtin.clone(), + manufacturer: "test_manufacturer".to_string(), + entity_code: "c7750265".to_string(), + }; + + let result = service + .add_gs1( + service_provider.clone(), + context.user_id.clone(), + input.clone(), + ) + .await + .unwrap(); + assert_eq!(result.gtin, new_gtin); + + // Try add another with same GTIN + let input = AddGS1 { + gtin: new_gtin.clone(), + manufacturer: "another_manufacturer".to_string(), + entity_code: "6d8482f7".to_string(), + }; + let result = service + .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .await; + + assert!(result.is_err()); + + // Delete the newly created gs1 + let _result = service + .delete_gs1( + service_provider.clone(), + context.user_id.clone(), + new_gtin.clone(), + ) + .await + .unwrap(); + } + + #[actix_rt::test] + async fn add_gs1_entity_code_not_found() { + let (_, _, connection_manager, _) = + setup_all("add_gs1_entity_code_not_found", MockDataInserts::none()).await; + + let service_provider = Arc::new(ServiceProvider::new( + connection_manager, + get_test_settings(""), + )); + let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); + let service = &context.service_provider.gs1_service; + + let new_gtin = uuid(); + let input = AddGS1 { + gtin: new_gtin.clone(), + manufacturer: "test_manufacturer".to_string(), + entity_code: "doesn't exist".to_string(), + }; + + let result = service + .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .await; + assert!(result.is_err()); + } +} diff --git a/backend/service/src/gs1/upsert.rs b/backend/service/src/gs1/upsert.rs new file mode 100644 index 000000000..49a46bb9e --- /dev/null +++ b/backend/service/src/gs1/upsert.rs @@ -0,0 +1,115 @@ +use std::sync::Arc; + +use crate::{ + audit_log::audit_log_entry, + service_provider::{ServiceContext, ServiceProvider}, +}; +use chrono::Utc; +use dgraph::{ + entity, + gs1::gs1::gs1_by_gtin, + insert_gs1::{insert_gs1, EntityCode, GS1Input}, + GS1, +}; +use repository::LogType; + +use super::ModifyGS1Error; + +#[derive(Clone, Debug)] +pub struct AddGS1 { + pub gtin: String, + pub manufacturer: String, + pub entity_code: String, +} + +pub async fn add_gs1( + sp: Arc, + user_id: String, + client: dgraph::DgraphClient, + new_gs1: AddGS1, +) -> Result { + // Validate + validate(&client, &new_gs1).await?; + + // Generate + let gs1_input = generate(new_gs1); + + let _result = insert_gs1(&client, gs1_input.clone(), true).await?; + + // Audit logging + let service_context = ServiceContext::with_user(sp.clone(), user_id)?; + audit_log_entry( + &service_context, + LogType::GS1Created, + Some(gs1_input.gtin.clone()), + Utc::now().naive_utc(), + )?; + + // Query to get the newly created gs1 + let result = gs1_by_gtin(&client, gs1_input.gtin).await.map_err(|e| { + ModifyGS1Error::InternalError(format!( + "Failed to get newly created gs1 by gtin: {}", + e.message() + )) + })?; + + let result = match result { + Some(result) => result, + None => { + return Err(ModifyGS1Error::InternalError( + "Unable to find newly created gs1".to_string(), + )) + } + }; + + Ok(result) +} + +pub fn generate(new_gs1: AddGS1) -> GS1Input { + GS1Input { + gtin: new_gs1.gtin.clone(), + manufacturer: new_gs1.manufacturer.clone(), + entity: EntityCode { + code: new_gs1.entity_code.clone(), + }, + } +} + +pub async fn validate( + _client: &dgraph::DgraphClient, + new_gs1: &AddGS1, +) -> Result<(), ModifyGS1Error> { + if new_gs1.gtin.is_empty() { + return Err(ModifyGS1Error::InternalError( + "GTIN is required".to_string(), + )); + } + + if new_gs1.manufacturer.is_empty() { + return Err(ModifyGS1Error::InternalError( + "Manufacturer is required".to_string(), + )); + } + + if new_gs1.entity_code.is_empty() { + return Err(ModifyGS1Error::InternalError( + "Entity code is required".to_string(), + )); + } + + let existing = gs1_by_gtin(_client, new_gs1.gtin.clone()).await?; + + match existing { + Some(_) => return Err(ModifyGS1Error::GS1AlreadyExists), + None => {} + } + + let entity = entity::entity_by_code(_client, new_gs1.entity_code.clone()).await?; + + match entity { + Some(_) => {} + None => return Err(ModifyGS1Error::UniversalCodeDoesNotExist), + } + + Ok(()) +} diff --git a/backend/service/src/lib.rs b/backend/service/src/lib.rs index cd5c25199..c39317726 100644 --- a/backend/service/src/lib.rs +++ b/backend/service/src/lib.rs @@ -6,6 +6,7 @@ pub mod auth; pub mod auth_data; pub mod configuration; pub mod email; +pub mod gs1; pub mod log_service; pub mod login; pub mod service_provider; diff --git a/backend/service/src/service_provider.rs b/backend/service/src/service_provider.rs index 5a0b974cc..dd2d998fb 100644 --- a/backend/service/src/service_provider.rs +++ b/backend/service/src/service_provider.rs @@ -6,6 +6,7 @@ use crate::{ auth::{AuthService, AuthServiceTrait}, configuration::ConfigurationService, email::{EmailService, EmailServiceTrait}, + gs1::GS1Service, log_service::{LogService, LogServiceTrait}, settings::Settings, universal_codes::UniversalCodesService, @@ -17,6 +18,7 @@ pub struct ServiceProvider { pub email_service: Box, pub universal_codes_service: Box, pub configuration_service: Box, + pub gs1_service: Box, pub validation_service: Box, pub user_account_service: Box, pub settings: Settings, @@ -70,6 +72,7 @@ impl ServiceProvider { email_service: Box::new(EmailService::new(settings.clone())), universal_codes_service: Box::new(UniversalCodesService::new(settings.clone())), configuration_service: Box::new(ConfigurationService::new(settings.clone())), + gs1_service: Box::new(GS1Service::new(settings.clone())), validation_service: Box::new(AuthService::new()), user_account_service: Box::new(UserAccountService {}), settings, From 1de52fdb922b25c90dc71260196c8a1c3bddf00a Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Tue, 30 Jan 2024 16:28:41 +1300 Subject: [PATCH 05/38] add graphQL gs1 layer --- backend/graphql/Cargo.toml | 1 + backend/graphql/gs1/Cargo.toml | 16 ++++++ backend/graphql/gs1/src/lib.rs | 42 ++++++++++++++++ backend/graphql/gs1/src/mutations/delete.rs | 53 +++++++++++++++++++ backend/graphql/gs1/src/mutations/insert.rs | 34 +++++++++++++ backend/graphql/gs1/src/mutations/mod.rs | 28 +++++++++++ backend/graphql/gs1/src/types/gs1.rs | 56 +++++++++++++++++++++ backend/graphql/gs1/src/types/inputs.rs | 19 +++++++ backend/graphql/gs1/src/types/mod.rs | 4 ++ backend/graphql/lib.rs | 5 ++ frontend/common/src/types/schema.ts | 40 +++++++++++++++ 11 files changed, 298 insertions(+) create mode 100644 backend/graphql/gs1/Cargo.toml create mode 100644 backend/graphql/gs1/src/lib.rs create mode 100644 backend/graphql/gs1/src/mutations/delete.rs create mode 100644 backend/graphql/gs1/src/mutations/insert.rs create mode 100644 backend/graphql/gs1/src/mutations/mod.rs create mode 100644 backend/graphql/gs1/src/types/gs1.rs create mode 100644 backend/graphql/gs1/src/types/inputs.rs create mode 100644 backend/graphql/gs1/src/types/mod.rs diff --git a/backend/graphql/Cargo.toml b/backend/graphql/Cargo.toml index 9f334f261..ca0b92131 100644 --- a/backend/graphql/Cargo.toml +++ b/backend/graphql/Cargo.toml @@ -13,6 +13,7 @@ service = { path = "../service" } util = { path = "../util" } graphql_configuration = { path = "configuration" } graphql_core = { path = "core" } +graphql_gs1 = { path = "gs1" } graphql_types = { path = "types" } graphql_general = { path = "general" } graphql_user_account = { path = "user_account" } diff --git a/backend/graphql/gs1/Cargo.toml b/backend/graphql/gs1/Cargo.toml new file mode 100644 index 000000000..8703a17f5 --- /dev/null +++ b/backend/graphql/gs1/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "graphql_gs1" +version = "0.1.0" +edition = "2018" + +[lib] +path = "src/lib.rs" +doctest = false + +[dependencies] + +graphql_core = { path = "../core" } +dgraph = { path = "../../dgraph" } +service = { path = "../../service" } +async-graphql = { version = "3.0.35", features = ["dataloader", "chrono"] } +graphql_universal_codes_v1 = { path = "../../graphql_v1/universal_codes" } diff --git a/backend/graphql/gs1/src/lib.rs b/backend/graphql/gs1/src/lib.rs new file mode 100644 index 000000000..1b1bc713d --- /dev/null +++ b/backend/graphql/gs1/src/lib.rs @@ -0,0 +1,42 @@ +mod mutations; +use self::mutations::*; +mod types; + +use async_graphql::*; +use graphql_core::ContextExt; +use types::{AddGS1Input, GS1CollectionConnector, GS1CollectionResponse, GS1Response}; + +#[derive(Default, Clone)] +pub struct GS1Queries; + +#[Object] +impl GS1Queries { + /// Get all GS1s + pub async fn gs1_barcodes( + &self, + ctx: &Context<'_>, + // order_by: Option, + // first: Option, + // offset: Option, + ) -> Result { + let result = ctx.service_provider().gs1_service.gs1s().await?; + + Ok(GS1CollectionResponse::Response( + GS1CollectionConnector::from_domain(result), + )) + } +} + +#[derive(Default, Clone)] +pub struct GS1Mutations; + +#[Object] +impl GS1Mutations { + async fn add_gs1(&self, ctx: &Context<'_>, input: AddGS1Input) -> Result { + add_gs1(ctx, input).await + } + + async fn delete_gs1(&self, ctx: &Context<'_>, gtin: String) -> Result { + delete_gs1(ctx, gtin).await + } +} diff --git a/backend/graphql/gs1/src/mutations/delete.rs b/backend/graphql/gs1/src/mutations/delete.rs new file mode 100644 index 000000000..37c4b58c2 --- /dev/null +++ b/backend/graphql/gs1/src/mutations/delete.rs @@ -0,0 +1,53 @@ +use async_graphql::*; + +use graphql_core::{ + standard_graphql_error::{validate_auth, StandardGraphqlError}, + ContextExt, +}; + +use service::{ + auth::{Resource, ResourceAccessRequest}, + gs1::ModifyGS1Error, +}; + +pub async fn delete_gs1(ctx: &Context<'_>, gtin: String) -> Result { + let user = validate_auth( + ctx, + &ResourceAccessRequest { + resource: Resource::MutateUniversalCodes, + }, + )?; + + let service_context = ctx.service_context(Some(&user))?; + match service_context + .service_provider + .gs1_service + .delete_gs1( + ctx.service_provider(), + service_context.user_id.clone(), + gtin, + ) + .await + { + Ok(affected_items) => Ok(affected_items), + Err(error) => map_modify_gs1_error(error), + } +} + +fn map_modify_gs1_error(error: ModifyGS1Error) -> Result { + use StandardGraphqlError::*; + let formatted_error = format!("{:#?}", error); + + let graphql_error = match error { + ModifyGS1Error::GS1AlreadyExists => BadUserInput(formatted_error), + ModifyGS1Error::GS1DoesNotExist => BadUserInput(formatted_error), + ModifyGS1Error::UniversalCodeDoesNotExist => BadUserInput(formatted_error), + ModifyGS1Error::InternalError(message) => InternalError(message), + ModifyGS1Error::DatabaseError(_) => InternalError(formatted_error), + ModifyGS1Error::DgraphError(gql_error) => { + InternalError(format!("{:#?} - {:?}", gql_error, gql_error.json())) + } + }; + + Err(graphql_error.extend()) +} diff --git a/backend/graphql/gs1/src/mutations/insert.rs b/backend/graphql/gs1/src/mutations/insert.rs new file mode 100644 index 000000000..015e46259 --- /dev/null +++ b/backend/graphql/gs1/src/mutations/insert.rs @@ -0,0 +1,34 @@ +use async_graphql::*; + +use graphql_core::{standard_graphql_error::validate_auth, ContextExt}; + +use service::auth::{Resource, ResourceAccessRequest}; + +use crate::{ + map_modify_gs1_error, + types::{AddGS1Input, GS1Node, GS1Response}, +}; + +pub async fn add_gs1(ctx: &Context<'_>, input: AddGS1Input) -> Result { + let user = validate_auth( + ctx, + &ResourceAccessRequest { + resource: Resource::MutateUniversalCodes, + }, + )?; + + let service_context = ctx.service_context(Some(&user))?; + match service_context + .service_provider + .gs1_service + .add_gs1( + ctx.service_provider(), + service_context.user_id.clone(), + input.into(), + ) + .await + { + Ok(gs1) => Ok(GS1Response::Response(GS1Node::from_domain(gs1))), + Err(error) => map_modify_gs1_error(error), + } +} diff --git a/backend/graphql/gs1/src/mutations/mod.rs b/backend/graphql/gs1/src/mutations/mod.rs new file mode 100644 index 000000000..634f21a67 --- /dev/null +++ b/backend/graphql/gs1/src/mutations/mod.rs @@ -0,0 +1,28 @@ +use async_graphql::*; +use graphql_core::standard_graphql_error::StandardGraphqlError; +use service::gs1::ModifyGS1Error; + +mod delete; +pub use delete::*; +mod insert; +pub use insert::*; + +use crate::types::GS1Response; + +pub fn map_modify_gs1_error(error: ModifyGS1Error) -> Result { + use StandardGraphqlError::*; + let formatted_error = format!("{:#?}", error); + + let graphql_error = match error { + ModifyGS1Error::GS1AlreadyExists => BadUserInput(formatted_error), + ModifyGS1Error::GS1DoesNotExist => BadUserInput(formatted_error), + ModifyGS1Error::UniversalCodeDoesNotExist => BadUserInput(formatted_error), + ModifyGS1Error::InternalError(message) => InternalError(message), + ModifyGS1Error::DatabaseError(_) => InternalError(formatted_error), + ModifyGS1Error::DgraphError(gql_error) => { + InternalError(format!("{:#?} - {:?}", gql_error, gql_error.json())) + } + }; + + Err(graphql_error.extend()) +} diff --git a/backend/graphql/gs1/src/types/gs1.rs b/backend/graphql/gs1/src/types/gs1.rs new file mode 100644 index 000000000..2b57b1c87 --- /dev/null +++ b/backend/graphql/gs1/src/types/gs1.rs @@ -0,0 +1,56 @@ +use async_graphql::*; +use dgraph::GS1; +use graphql_universal_codes_v1::EntityType; +use service::gs1::GS1Collection; + +#[derive(Clone, Debug)] +pub struct GS1Node { + pub row: GS1, +} + +impl GS1Node { + pub fn from_domain(gs1: GS1) -> GS1Node { + GS1Node { row: gs1 } + } +} + +#[Object] +impl GS1Node { + pub async fn id(&self) -> &str { + &self.row.gtin + } + + pub async fn gtin(&self) -> &str { + &self.row.gtin + } + pub async fn manufacturer(&self) -> &str { + &self.row.manufacturer + } + pub async fn r#type(&self) -> EntityType { + EntityType::from_domain(self.row.entity.clone()) + } +} + +#[derive(Debug, SimpleObject)] +pub struct GS1CollectionConnector { + pub data: Vec, + pub total_count: u32, +} + +impl GS1CollectionConnector { + pub fn from_domain(results: GS1Collection) -> GS1CollectionConnector { + GS1CollectionConnector { + total_count: results.total_length, + data: results.data.into_iter().map(GS1Node::from_domain).collect(), + } + } +} + +#[derive(Union)] +pub enum GS1Response { + Response(GS1Node), +} +#[derive(Union)] +pub enum GS1CollectionResponse { + Response(GS1CollectionConnector), +} diff --git a/backend/graphql/gs1/src/types/inputs.rs b/backend/graphql/gs1/src/types/inputs.rs new file mode 100644 index 000000000..043977313 --- /dev/null +++ b/backend/graphql/gs1/src/types/inputs.rs @@ -0,0 +1,19 @@ +use async_graphql::*; +use service::gs1::upsert::AddGS1; + +#[derive(InputObject, Clone)] +pub struct AddGS1Input { + pub gtin: String, + pub manufacturer: String, + pub entity_code: String, +} + +impl From for AddGS1 { + fn from(input: AddGS1Input) -> Self { + AddGS1 { + gtin: input.gtin, + manufacturer: input.manufacturer, + entity_code: input.entity_code, + } + } +} diff --git a/backend/graphql/gs1/src/types/mod.rs b/backend/graphql/gs1/src/types/mod.rs new file mode 100644 index 000000000..48c70de12 --- /dev/null +++ b/backend/graphql/gs1/src/types/mod.rs @@ -0,0 +1,4 @@ +mod gs1; +pub use gs1::*; +mod inputs; +pub use inputs::*; diff --git a/backend/graphql/lib.rs b/backend/graphql/lib.rs index 5a3e58936..17edaefb1 100644 --- a/backend/graphql/lib.rs +++ b/backend/graphql/lib.rs @@ -11,6 +11,7 @@ use graphql_configuration::{ConfigurationMutations, ConfigurationQueries}; use graphql_core::loader::LoaderRegistry; use graphql_core::{refresh_token_from_cookie, RefreshTokenData, SelfRequest}; use graphql_general::GeneralQueries; +use graphql_gs1::{GS1Mutations, GS1Queries}; use graphql_universal_codes::{UniversalCodesMutations, UniversalCodesQueries}; use graphql_universal_codes_v1::UniversalCodesV1Queries; use graphql_user_account::{UserAccountMutations, UserAccountQueries}; @@ -29,6 +30,7 @@ pub struct FullQuery( pub UniversalCodesQueries, pub UniversalCodesV1Queries, pub ConfigurationQueries, + pub GS1Queries, ); #[derive(MergedObject, Default, Clone)] @@ -36,6 +38,7 @@ pub struct FullMutation( pub UserAccountMutations, pub UniversalCodesMutations, pub ConfigurationMutations, + pub GS1Mutations, ); pub type Schema = async_graphql::Schema; @@ -48,6 +51,7 @@ pub fn full_query() -> FullQuery { UniversalCodesQueries, UniversalCodesV1Queries, ConfigurationQueries, + GS1Queries, ) } @@ -56,6 +60,7 @@ pub fn full_mutation() -> FullMutation { UserAccountMutations, UniversalCodesMutations, ConfigurationMutations, + GS1Mutations, ) } diff --git a/frontend/common/src/types/schema.ts b/frontend/common/src/types/schema.ts index a631daae4..f672024f2 100644 --- a/frontend/common/src/types/schema.ts +++ b/frontend/common/src/types/schema.ts @@ -32,6 +32,12 @@ export type AddConfigurationItemInput = { type: ConfigurationItemTypeInput; }; +export type AddGs1Input = { + entityCode: Scalars['String']['input']; + gtin: Scalars['String']['input']; + manufacturer: Scalars['String']['input']; +}; + export type AlternativeNameInput = { code: Scalars['String']['input']; name: Scalars['String']['input']; @@ -180,9 +186,11 @@ export type FullMutation = { /** Updates user account based on a token and their information (Response to initiate_user_invite) */ acceptUserInvite: InviteUserResponse; addConfigurationItem: Scalars['Int']['output']; + addGs1: Gs1Response; approvePendingChange: UpsertEntityResponse; createUserAccount: CreateUserAccountResponse; deleteConfigurationItem: Scalars['Int']['output']; + deleteGs1: Scalars['Int']['output']; deleteUserAccount: DeleteUserAccountResponse; /** * Initiates the password reset flow for a user based on email address @@ -214,6 +222,11 @@ export type FullMutationAddConfigurationItemArgs = { }; +export type FullMutationAddGs1Args = { + input: AddGs1Input; +}; + + export type FullMutationApprovePendingChangeArgs = { input: UpsertEntityInput; requestId: Scalars['String']['input']; @@ -230,6 +243,11 @@ export type FullMutationDeleteConfigurationItemArgs = { }; +export type FullMutationDeleteGs1Args = { + gtin: Scalars['String']['input']; +}; + + export type FullMutationDeleteUserAccountArgs = { userAccountId: Scalars['String']['input']; }; @@ -294,6 +312,8 @@ export type FullQuery = { entities: EntityCollectionType; /** Query "universal codes" entry by code */ entity?: Maybe; + /** Get all GS1s */ + gs1Barcodes: Gs1CollectionResponse; logout: LogoutResponse; logs: LogResponse; me: UserResponse; @@ -364,6 +384,24 @@ export type FullQueryUserAccountsArgs = { sort?: InputMaybe>; }; +export type Gs1CollectionConnector = { + __typename: 'Gs1CollectionConnector'; + data: Array; + totalCount: Scalars['Int']['output']; +}; + +export type Gs1CollectionResponse = Gs1CollectionConnector; + +export type Gs1Node = { + __typename: 'Gs1Node'; + gtin: Scalars['String']['output']; + id: Scalars['String']['output']; + manufacturer: Scalars['String']['output']; + type: EntityType; +}; + +export type Gs1Response = Gs1Node; + export type IdResponse = { __typename: 'IdResponse'; id: Scalars['String']['output']; @@ -424,6 +462,8 @@ export type LogNode = { export enum LogNodeType { ConfigurationItemCreated = 'CONFIGURATION_ITEM_CREATED', ConfigurationItemDeleted = 'CONFIGURATION_ITEM_DELETED', + Gs1Created = 'GS1_CREATED', + Gs1Deleted = 'GS1_DELETED', PropertyConfigurationItemUpserted = 'PROPERTY_CONFIGURATION_ITEM_UPSERTED', UniversalCodeChangeApproved = 'UNIVERSAL_CODE_CHANGE_APPROVED', UniversalCodeChangeRejected = 'UNIVERSAL_CODE_CHANGE_REJECTED', From 8c97d3604ae2fa7ac708084782bbc65952ac7c2e Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Tue, 30 Jan 2024 17:34:27 +1300 Subject: [PATCH 06/38] add GS1 table view --- backend/graphql/gs1/src/types/gs1.rs | 2 +- .../common/src/intl/locales/en/common.json | 4 ++ frontend/common/src/intl/locales/en/host.json | 1 + frontend/common/src/types/schema.ts | 2 +- frontend/config/src/routes.ts | 1 + .../src/Admin/GS1Barcodes/GS1ListView.tsx | 47 +++++++++++++++++ .../src/Admin/GS1Barcodes/api/hooks/index.ts | 1 + .../GS1Barcodes/api/hooks/useGS1Barcodes.ts | 13 +++++ .../system/src/Admin/GS1Barcodes/api/index.ts | 1 + .../GS1Barcodes/api/operations.generated.ts | 50 +++++++++++++++++++ .../Admin/GS1Barcodes/api/operations.graphql | 21 ++++++++ .../system/src/Admin/GS1Barcodes/index.ts | 1 + frontend/system/src/Admin/Service.tsx | 2 + frontend/system/src/queryKeys.ts | 1 + 14 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/index.ts create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/operations.graphql create mode 100644 frontend/system/src/Admin/GS1Barcodes/index.ts diff --git a/backend/graphql/gs1/src/types/gs1.rs b/backend/graphql/gs1/src/types/gs1.rs index 2b57b1c87..33b63083b 100644 --- a/backend/graphql/gs1/src/types/gs1.rs +++ b/backend/graphql/gs1/src/types/gs1.rs @@ -26,7 +26,7 @@ impl GS1Node { pub async fn manufacturer(&self) -> &str { &self.row.manufacturer } - pub async fn r#type(&self) -> EntityType { + pub async fn entity(&self) -> EntityType { EntityType::from_domain(self.row.entity.clone()) } } diff --git a/frontend/common/src/intl/locales/en/common.json b/frontend/common/src/intl/locales/en/common.json index 492c314a2..a7ab0d2be 100644 --- a/frontend/common/src/intl/locales/en/common.json +++ b/frontend/common/src/intl/locales/en/common.json @@ -88,6 +88,7 @@ "label.expand-all": "Expand all", "label.expiry": "Expiry", "label.event": "Event", + "label.gtin": "GTIN", "label.hours": "Hours", "label.hours_one": "Hour", "label.hours_other": "Hours", @@ -103,6 +104,7 @@ "label.location": "Location", "label.log": "Log", "label.manage": "Manage", + "label.manufacturer": "Manufacturer", "label.message": "Message", "label.minutes": "Minute(s)", "label.minutes_one": "Minute", @@ -115,6 +117,7 @@ "label.notes": "Notes", "label.number": "Number", "label.of": "of", + "label.pack-size": "Pack Size", "label.phone": "Phone", "label.please-specify": "Please specify", "label.quantity": "Quantity", @@ -125,6 +128,7 @@ "label.daily": "Daily", "label.weekly": "Weekly", "label.monthly": "Monthly", + "label.product": "Product", "label.request-for": "Request For", "label.requested-by": "Requested By", "label.select": "Select", diff --git a/frontend/common/src/intl/locales/en/host.json b/frontend/common/src/intl/locales/en/host.json index cce4bd15e..789f75b8d 100644 --- a/frontend/common/src/intl/locales/en/host.json +++ b/frontend/common/src/intl/locales/en/host.json @@ -10,6 +10,7 @@ "auth.timeout-message": "You have been logged out of your session due to inactivity. Click OK to return to the login screen.", "auth.timeout-title": "Session Timed Out", "auth.unauthenticated-message": "You are not currently logged in. Click OK to return to the login screen.", + "barcodes": "Barcodes", "browse": "Browse", "button.activate-account": "Activate Account", "button.close-the-menu": "Close the menu", diff --git a/frontend/common/src/types/schema.ts b/frontend/common/src/types/schema.ts index f672024f2..6423df9ee 100644 --- a/frontend/common/src/types/schema.ts +++ b/frontend/common/src/types/schema.ts @@ -394,10 +394,10 @@ export type Gs1CollectionResponse = Gs1CollectionConnector; export type Gs1Node = { __typename: 'Gs1Node'; + entity: EntityType; gtin: Scalars['String']['output']; id: Scalars['String']['output']; manufacturer: Scalars['String']['output']; - type: EntityType; }; export type Gs1Response = Gs1Node; diff --git a/frontend/config/src/routes.ts b/frontend/config/src/routes.ts index ba0d4cccc..04bf62d2f 100644 --- a/frontend/config/src/routes.ts +++ b/frontend/config/src/routes.ts @@ -13,6 +13,7 @@ export enum AppRoute { Configuration = 'configuration', Edit = 'edit', PendingChanges = 'pending-changes', + GS1Barcodes = 'barcodes', Settings = 'settings', Logout = 'logout', diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx new file mode 100644 index 000000000..b1ff985e7 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -0,0 +1,47 @@ +import { + createTableStore, + DataTable, + NothingHere, + TableProvider, + useColumns, +} from '@common/ui'; +import React from 'react'; +import { useGS1Barcodes } from './api'; +import { Gs1Fragment } from './api/operations.generated'; + +export const GS1ListView = () => { + const columns = useColumns( + [ + { + key: 'entity', + label: 'label.product', + Cell: ({ rowData }) => <>{rowData.entity?.description}, + }, + { + key: 'also entity', // TODO does this have implications... + label: 'label.pack-size', + Cell: ({ rowData }) => <>{rowData.entity?.name}, + }, + { key: 'manufacturer', label: 'label.manufacturer' }, + { key: 'id', label: 'label.gtin' }, + ], + {}, + [] + ); + + const { data, isError, isLoading } = useGS1Barcodes(); + + return ( + + } + // pagination={false} // TODO + // onChangePage={updatePaginationQuery} + /> + + ); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts new file mode 100644 index 000000000..8b64a20e4 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts @@ -0,0 +1 @@ +export * from './useGS1Barcodes'; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts new file mode 100644 index 000000000..0ee3d4c90 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts @@ -0,0 +1,13 @@ +import { useGql, useQuery } from 'frontend/common/src'; +import { GS1_BARCODES_KEY } from '../../../../queryKeys'; +import { getSdk } from '../operations.generated'; + +export const useGS1Barcodes = () => { + const { client } = useGql(); + const sdk = getSdk(client); + const cacheKeys = [GS1_BARCODES_KEY]; + return useQuery(cacheKeys, async () => { + const response = await sdk.Gs1Barcodes(); + return response?.gs1Barcodes?.data ?? []; + }); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/index.ts b/frontend/system/src/Admin/GS1Barcodes/api/index.ts new file mode 100644 index 000000000..4cc90d02b --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/index.ts @@ -0,0 +1 @@ +export * from './hooks'; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts new file mode 100644 index 000000000..303fcf60f --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts @@ -0,0 +1,50 @@ +import * as Types from '@uc-frontend/common'; + +import { GraphQLClient } from 'graphql-request'; +import * as Dom from 'graphql-request/dist/types.dom'; +import gql from 'graphql-tag'; +export type Gs1Fragment = { __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }; + +export type Gs1BarcodesQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type Gs1BarcodesQuery = { __typename?: 'FullQuery', gs1Barcodes: { __typename?: 'Gs1CollectionConnector', totalCount: number, data: Array<{ __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }> } }; + +export const Gs1FragmentDoc = gql` + fragment GS1 on Gs1Node { + id + gtin + manufacturer + entity { + code + name + description + } +} + `; +export const Gs1BarcodesDocument = gql` + query Gs1Barcodes { + gs1Barcodes { + ... on Gs1CollectionConnector { + data { + ...GS1 + } + totalCount + } + } +} + ${Gs1FragmentDoc}`; + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + Gs1Barcodes(variables?: Gs1BarcodesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(Gs1BarcodesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'Gs1Barcodes', 'query'); + } + }; +} +export type Sdk = ReturnType; \ No newline at end of file diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql new file mode 100644 index 000000000..642188cd2 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql @@ -0,0 +1,21 @@ +fragment GS1 on Gs1Node { + id + gtin + manufacturer + entity { + code + name + description + } +} + +query Gs1Barcodes { + gs1Barcodes { + ... on Gs1CollectionConnector { + data { + ...GS1 + } + totalCount + } + } +} diff --git a/frontend/system/src/Admin/GS1Barcodes/index.ts b/frontend/system/src/Admin/GS1Barcodes/index.ts new file mode 100644 index 000000000..a909abcf1 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/index.ts @@ -0,0 +1 @@ +export * from './GS1ListView'; diff --git a/frontend/system/src/Admin/Service.tsx b/frontend/system/src/Admin/Service.tsx index 0ff7a0b9e..887c76a90 100644 --- a/frontend/system/src/Admin/Service.tsx +++ b/frontend/system/src/Admin/Service.tsx @@ -5,6 +5,7 @@ import { AppRoute } from 'frontend/config/src'; import { UserAccountListView } from './Users/ListView'; import { ConfigurationTabsView } from './Configuration'; import { PendingChangeDetails, PendingChangesListView } from './PendingChanges'; +import { GS1ListView } from './GS1Barcodes'; const AdminService = () => { return ( @@ -27,6 +28,7 @@ const AdminService = () => { path={`/${AppRoute.PendingChanges}/:id`} element={} /> + } /> } diff --git a/frontend/system/src/queryKeys.ts b/frontend/system/src/queryKeys.ts index bfccb11fb..aee67d074 100644 --- a/frontend/system/src/queryKeys.ts +++ b/frontend/system/src/queryKeys.ts @@ -3,3 +3,4 @@ export const ENTITIES_KEY = 'ENTITIES'; export const PENDING_CHANGE_KEY = 'PENDING_CHANGE'; export const PENDING_CHANGES_KEY = 'PENDING_CHANGES'; export const PROPERTY_CONFIG_ITEMS_KEY = 'PROPERTY_CONFIG_ITEMS'; +export const GS1_BARCODES_KEY = 'GS1_BARCODES'; From e82e5c8a19a14731298737ac2bb93c61bf9fa181 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 08:46:40 +1300 Subject: [PATCH 07/38] add basic addGS1 modal --- .../common/src/intl/locales/en/system.json | 2 + .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 111 ++++++++++++++++++ .../src/Admin/GS1Barcodes/GS1ListView.tsx | 21 ++++ .../src/Admin/GS1Barcodes/api/hooks/index.ts | 1 + .../Admin/GS1Barcodes/api/hooks/useAddGS1.ts | 17 +++ .../GS1Barcodes/api/operations.generated.ts | 17 +++ .../Admin/GS1Barcodes/api/operations.graphql | 6 + 7 files changed, 175 insertions(+) create mode 100644 frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/hooks/useAddGS1.ts diff --git a/frontend/common/src/intl/locales/en/system.json b/frontend/common/src/intl/locales/en/system.json index 043c322ab..18e335fd1 100644 --- a/frontend/common/src/intl/locales/en/system.json +++ b/frontend/common/src/intl/locales/en/system.json @@ -28,6 +28,7 @@ "helper-text.website-placeholder": "You can use a placeholder for the property code: e.g. https://link.com?search={{code}}", "helper-text.you-cant-change-this": "Careful! You won't be able to change this value.", "label.about": "About", + "label.add-barcode": "Add Barcode", "label.add-brand": "Add Brand", "label.add-active-ingredients": "Add Active Ingredients", "label.add-alternative-name": "Add Alternative Name", @@ -70,6 +71,7 @@ "label.new-user": "New User", "label.new-vaccine": "New Vaccine", "label.pack-size": "Pack Size", + "label.pack-size-code": "Universal Code for Pack Size", "label.pack-sizes": "Pack Sizes", "label.presentations": "Presentations (Size, Type or Strength)", "label.presentation": "Presentation", diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx new file mode 100644 index 000000000..709d4c1d2 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -0,0 +1,111 @@ +import { + BasicTextInput, + DialogButton, + InlineSpinner, + LoadingButton, +} from '@common/components'; +import { useDialog } from '@common/hooks'; +import { CheckIcon } from '@common/icons'; +import { useTranslation } from '@common/intl'; +import { AddGs1Input } from '@common/types'; +import { Grid } from '@common/ui'; +import { Alert, AlertTitle } from '@mui/material'; +import React, { useState } from 'react'; +import { useAddGS1 } from './api'; + +type GS1EditModalProps = { + isOpen: boolean; + onClose: () => void; +}; + +export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { + const t = useTranslation('system'); + + const [errorMessage, setErrorMessage] = useState(null); + const [draft, setDraft] = useState({ + entityCode: '', + gtin: '', + manufacturer: '', + }); + + const { Modal } = useDialog({ isOpen, onClose }); + + const { mutateAsync: addGs1, isLoading } = useAddGS1(); + + const onSubmit = async () => { + try { + await addGs1({ + input: { + ...draft, + }, + }); + onClose(); + } catch (err) { + if (err instanceof Error) setErrorMessage(err.message); + else setErrorMessage(t('messages.unknown-error')); + } + }; + + const isInvalid = !draft.gtin || !draft.manufacturer || !draft.entityCode; + const modalWidth = Math.min(window.innerWidth - 200, 800); + + return ( + } + variant="contained" + > + {t('button.ok')} + + } + cancelButton={} + title={t('label.add-barcode')} + > + {isLoading ? ( + + ) : ( + + setDraft({ ...draft, gtin: e.target.value })} + /> + setDraft({ ...draft, manufacturer: e.target.value })} + /> + setDraft({ ...draft, entityCode: e.target.value })} + /> + {errorMessage ? ( + + { + setErrorMessage(''); + }} + > + {t('error')} + {errorMessage} + + + ) : null} + + )} + + ); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx index b1ff985e7..0dcdb285e 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -1,15 +1,25 @@ +import { useEditModal } from '@common/hooks'; +import { useTranslation } from '@common/intl'; import { + AppBarButtonsPortal, createTableStore, DataTable, + LoadingButton, NothingHere, + PlusCircleIcon, TableProvider, useColumns, } from '@common/ui'; import React from 'react'; import { useGS1Barcodes } from './api'; import { Gs1Fragment } from './api/operations.generated'; +import { GS1EditModal } from './GS1EditModal'; export const GS1ListView = () => { + const t = useTranslation('system'); + + const { onOpen, onClose, isOpen } = useEditModal(); + const columns = useColumns( [ { @@ -33,6 +43,17 @@ export const GS1ListView = () => { return ( + {isOpen && } + + onOpen()} + isLoading={false} + startIcon={} + > + {t('label.add-barcode')} + + + { + const { client } = useGql(); + const sdk = getSdk(client); + const queryClient = useQueryClient(); + + const invalidateQueries = () => { + queryClient.invalidateQueries([GS1_BARCODES_KEY]); + }; + + return useMutation(sdk.AddGs1, { + onSettled: invalidateQueries, + }); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts index 303fcf60f..56eaa2d2f 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts @@ -10,6 +10,13 @@ export type Gs1BarcodesQueryVariables = Types.Exact<{ [key: string]: never; }>; export type Gs1BarcodesQuery = { __typename?: 'FullQuery', gs1Barcodes: { __typename?: 'Gs1CollectionConnector', totalCount: number, data: Array<{ __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }> } }; +export type AddGs1MutationVariables = Types.Exact<{ + input: Types.AddGs1Input; +}>; + + +export type AddGs1Mutation = { __typename?: 'FullMutation', addGs1: { __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } } }; + export const Gs1FragmentDoc = gql` fragment GS1 on Gs1Node { id @@ -34,6 +41,13 @@ export const Gs1BarcodesDocument = gql` } } ${Gs1FragmentDoc}`; +export const AddGs1Document = gql` + mutation AddGs1($input: AddGS1Input!) { + addGs1(input: $input) { + ...GS1 + } +} + ${Gs1FragmentDoc}`; export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; @@ -44,6 +58,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = return { Gs1Barcodes(variables?: Gs1BarcodesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(Gs1BarcodesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'Gs1Barcodes', 'query'); + }, + AddGs1(variables: AddGs1MutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(AddGs1Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddGs1', 'mutation'); } }; } diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql index 642188cd2..aeca4d075 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql @@ -19,3 +19,9 @@ query Gs1Barcodes { } } } + +mutation AddGs1($input: AddGS1Input!) { + addGs1(input: $input) { + ...GS1 + } +} From 3fba4d9251b47ac62f4e71e780d666afe0634575 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 09:03:11 +1300 Subject: [PATCH 08/38] add barcodes link to app drawer --- frontend/common/src/intl/locales/en/host.json | 2 +- frontend/common/src/ui/icons/Barcode.tsx | 14 ++++++++++++++ frontend/common/src/ui/icons/index.ts | 1 + .../host/src/components/AppDrawer/AppDrawer.tsx | 8 ++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 frontend/common/src/ui/icons/Barcode.tsx diff --git a/frontend/common/src/intl/locales/en/host.json b/frontend/common/src/intl/locales/en/host.json index 789f75b8d..93056aed3 100644 --- a/frontend/common/src/intl/locales/en/host.json +++ b/frontend/common/src/intl/locales/en/host.json @@ -10,7 +10,7 @@ "auth.timeout-message": "You have been logged out of your session due to inactivity. Click OK to return to the login screen.", "auth.timeout-title": "Session Timed Out", "auth.unauthenticated-message": "You are not currently logged in. Click OK to return to the login screen.", - "barcodes": "Barcodes", + "barcodes": "GS1 Barcodes", "browse": "Browse", "button.activate-account": "Activate Account", "button.close-the-menu": "Close the menu", diff --git a/frontend/common/src/ui/icons/Barcode.tsx b/frontend/common/src/ui/icons/Barcode.tsx new file mode 100644 index 000000000..04a03c236 --- /dev/null +++ b/frontend/common/src/ui/icons/Barcode.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; + +export const BarcodeIcon = (props: SvgIconProps): JSX.Element => { + return ( + + + + ); +}; diff --git a/frontend/common/src/ui/icons/index.ts b/frontend/common/src/ui/icons/index.ts index 8a826b8c4..504c92ee3 100644 --- a/frontend/common/src/ui/icons/index.ts +++ b/frontend/common/src/ui/icons/index.ts @@ -3,6 +3,7 @@ export { AngleCircleRightIcon } from './AngleCircleRight'; export { ArrowLeftIcon } from './ArrowLeft'; export { ArrowRightIcon } from './ArrowRight'; export { BarChartIcon } from './BarChart'; +export { BarcodeIcon } from './Barcode'; export { BookIcon } from './Book'; export { CartIcon } from './Cart'; export { CheckboxCheckedIcon } from './CheckboxChecked'; diff --git a/frontend/host/src/components/AppDrawer/AppDrawer.tsx b/frontend/host/src/components/AppDrawer/AppDrawer.tsx index e6ab1c7b0..9a4b6f22e 100644 --- a/frontend/host/src/components/AppDrawer/AppDrawer.tsx +++ b/frontend/host/src/components/AppDrawer/AppDrawer.tsx @@ -20,6 +20,7 @@ import { ClockIcon, InfoIcon, EditIcon, + BarcodeIcon, } from '@uc-frontend/common'; import { AppRoute } from '@uc-frontend/config'; import { AppDrawerIcon } from './AppDrawerIcon'; @@ -203,6 +204,13 @@ export const AppDrawer: React.FC = () => { icon={} text={t('pending-changes')} /> + } + text={t('barcodes')} + /> Date: Wed, 31 Jan 2024 09:26:32 +1300 Subject: [PATCH 09/38] truncate description if too large --- .../src/Admin/GS1Barcodes/GS1ListView.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx index 0dcdb285e..ffd1af6bf 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -8,6 +8,8 @@ import { NothingHere, PlusCircleIcon, TableProvider, + Tooltip, + Typography, useColumns, } from '@common/ui'; import React from 'react'; @@ -20,17 +22,37 @@ export const GS1ListView = () => { const { onOpen, onClose, isOpen } = useEditModal(); + const removeName = (description: string, name: string) => { + const nameIndex = description.indexOf(name); + if (nameIndex === -1) return description; + return description.substring(0, nameIndex); + }; + const columns = useColumns( [ { key: 'entity', label: 'label.product', - Cell: ({ rowData }) => <>{rowData.entity?.description}, + Cell: ({ rowData }) => { + const description = removeName( + rowData.entity.description, + rowData.entity.name + ); + return ( + + + {description.length > 35 + ? description.substring(0, 35) + '...' + : description} + + + ); + }, }, { key: 'also entity', // TODO does this have implications... label: 'label.pack-size', - Cell: ({ rowData }) => <>{rowData.entity?.name}, + Cell: ({ rowData }) => <>{rowData.entity.name}, }, { key: 'manufacturer', label: 'label.manufacturer' }, { key: 'id', label: 'label.gtin' }, From 84567e9f6373af8fe6db2cfc5f5e0f571daeba16 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 09:42:55 +1300 Subject: [PATCH 10/38] add lookup button --- .../common/src/intl/locales/en/system.json | 1 + .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/common/src/intl/locales/en/system.json b/frontend/common/src/intl/locales/en/system.json index 18e335fd1..33015512f 100644 --- a/frontend/common/src/intl/locales/en/system.json +++ b/frontend/common/src/intl/locales/en/system.json @@ -66,6 +66,7 @@ "label.forms": "Forms", "label.immediate-packaging": "Immediate Packaging", "label.invite-user": "Invite User", + "label.lookup": "Look up", "label.new-consumable": "New Consumable", "label.new-drug": "New Drug", "label.new-user": "New User", diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx index 709d4c1d2..724882a0e 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -1,14 +1,15 @@ import { BasicTextInput, DialogButton, + FlatButton, InlineSpinner, LoadingButton, } from '@common/components'; import { useDialog } from '@common/hooks'; -import { CheckIcon } from '@common/icons'; +import { CheckIcon, SearchIcon } from '@common/icons'; import { useTranslation } from '@common/intl'; import { AddGs1Input } from '@common/types'; -import { Grid } from '@common/ui'; +import { Box, Grid } from '@common/ui'; import { Alert, AlertTitle } from '@mui/material'; import React, { useState } from 'react'; import { useAddGS1 } from './api'; @@ -85,12 +86,22 @@ export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { value={draft.manufacturer} onChange={e => setDraft({ ...draft, manufacturer: e.target.value })} /> - setDraft({ ...draft, entityCode: e.target.value })} - /> + + setDraft({ ...draft, entityCode: e.target.value })} + /> + } + onClick={() => {}} + label={t('label.lookup')} + /> + {errorMessage ? ( Date: Wed, 31 Jan 2024 11:52:50 +1300 Subject: [PATCH 11/38] autocomplete pack size code selection --- .../inputs/Autocomplete/AutocompleteList.tsx | 7 +- .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 85 ++++++++++++++----- .../src/Entities/api/operations.generated.ts | 5 +- .../src/Entities/api/operations.graphql | 1 + 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/frontend/common/src/ui/components/inputs/Autocomplete/AutocompleteList.tsx b/frontend/common/src/ui/components/inputs/Autocomplete/AutocompleteList.tsx index a87a371b2..dd70f3c5a 100644 --- a/frontend/common/src/ui/components/inputs/Autocomplete/AutocompleteList.tsx +++ b/frontend/common/src/ui/components/inputs/Autocomplete/AutocompleteList.tsx @@ -34,6 +34,7 @@ export type AutocompleteListProps = { disableClearable?: boolean; getOptionDisabled?: (option: T) => boolean; open?: boolean; + openOnFocus?: boolean; }; export const AutocompleteList = ({ @@ -59,6 +60,7 @@ export const AutocompleteList = ({ disableClearable, getOptionDisabled, open = true, + openOnFocus, }: AutocompleteListProps): JSX.Element => { const createdFilterOptions = createFilterOptions(filterOptionConfig); const optionRenderer = optionKey @@ -73,6 +75,8 @@ export const AutocompleteList = ({ mappedOptions = options; } + const openProp = !openOnFocus ? open : undefined; + return ( ({ renderInput || (props => ) } filterOptions={filterOptions ?? createdFilterOptions} - open={open} + open={openProp} forcePopupIcon={false} options={mappedOptions} renderOption={optionRenderer} @@ -116,6 +120,7 @@ export const AutocompleteList = ({ clearText={clearText} value={value} getOptionDisabled={getOptionDisabled} + openOnFocus={openOnFocus} /> ); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx index 724882a0e..b0293d1a1 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -1,17 +1,21 @@ import { + AutocompleteList, + AutocompleteOptionRenderer, BasicTextInput, DialogButton, - FlatButton, InlineSpinner, LoadingButton, } from '@common/components'; import { useDialog } from '@common/hooks'; -import { CheckIcon, SearchIcon } from '@common/icons'; +import { CheckIcon } from '@common/icons'; import { useTranslation } from '@common/intl'; import { AddGs1Input } from '@common/types'; -import { Box, Grid } from '@common/ui'; +import { Grid, Typography } from '@common/ui'; +import { RegexUtils } from '@common/utils'; import { Alert, AlertTitle } from '@mui/material'; import React, { useState } from 'react'; +import { useEntities } from '../../Entities/api'; +import { EntityRowFragment } from '../../Entities/api/operations.generated'; import { useAddGS1 } from './api'; type GS1EditModalProps = { @@ -31,6 +35,12 @@ export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { const { Modal } = useDialog({ isOpen, onClose }); + const { data: packSizeEntities } = useEntities({ + first: 10000, + filter: { type: 'PackSize', orderBy: { field: 'description' } }, + offset: 0, + }); + const { mutateAsync: addGs1, isLoading } = useAddGS1(); const onSubmit = async () => { @@ -47,6 +57,8 @@ export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { } }; + const packSizeOptions: EntityRowFragment[] = packSizeEntities?.data ?? []; + const isInvalid = !draft.gtin || !draft.manufacturer || !draft.entityCode; const modalWidth = Math.min(window.innerWidth - 200, 800); @@ -86,22 +98,35 @@ export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { value={draft.manufacturer} onChange={e => setDraft({ ...draft, manufacturer: e.target.value })} /> - - setDraft({ ...draft, entityCode: e.target.value })} - /> - } - onClick={() => {}} - label={t('label.lookup')} - /> - + `${option.id}`} + width={modalWidth - 50} + openOnFocus + renderInput={props => ( + + )} + filterOptions={(options, state) => + options.filter(option => + RegexUtils.matchObjectProperties(state.inputValue, option, [ + 'description', + 'code', + ]) + ) + } + onChange={(e, value) => + setDraft({ + ...draft, + entityCode: (value as unknown as EntityRowFragment)?.code ?? '', + }) + } + /> {errorMessage ? ( { ); }; +const getParentDescription = (description: string, name: string) => { + const nameIndex = description.lastIndexOf(name); + if (nameIndex === -1) return description; + return description.substring(0, nameIndex); +}; + +const renderOption: AutocompleteOptionRenderer = ( + props, + option +): JSX.Element => ( +
  • + + {option.code} + + + {getParentDescription(option.description, option.name)} + + {option.name} + + +
  • +); diff --git a/frontend/system/src/Entities/api/operations.generated.ts b/frontend/system/src/Entities/api/operations.generated.ts index bf756c1f0..04b28ec0e 100644 --- a/frontend/system/src/Entities/api/operations.generated.ts +++ b/frontend/system/src/Entities/api/operations.generated.ts @@ -3,7 +3,7 @@ import * as Types from '@uc-frontend/common'; import { GraphQLClient } from 'graphql-request'; import * as Dom from 'graphql-request/dist/types.dom'; import gql from 'graphql-tag'; -export type EntityRowFragment = { __typename?: 'EntityType', type: string, description: string, code: string, id: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', name: string }> }; +export type EntityRowFragment = { __typename?: 'EntityType', type: string, description: string, code: string, name: string, id: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', name: string }> }; export type EntityDetailsFragment = { __typename?: 'EntityType', code: string, name: string, type: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }; @@ -14,7 +14,7 @@ export type EntitiesQueryVariables = Types.Exact<{ }>; -export type EntitiesQuery = { __typename?: 'FullQuery', entities: { __typename?: 'EntityCollectionType', totalLength: number, data: Array<{ __typename?: 'EntityType', type: string, description: string, code: string, id: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', name: string }> }> } }; +export type EntitiesQuery = { __typename?: 'FullQuery', entities: { __typename?: 'EntityCollectionType', totalLength: number, data: Array<{ __typename?: 'EntityType', type: string, description: string, code: string, name: string, id: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', name: string }> }> } }; export type EntityQueryVariables = Types.Exact<{ code: Types.Scalars['String']['input']; @@ -36,6 +36,7 @@ export const EntityRowFragmentDoc = gql` type description code + name alternativeNames { name } diff --git a/frontend/system/src/Entities/api/operations.graphql b/frontend/system/src/Entities/api/operations.graphql index 75405bc07..5a1f5fc46 100644 --- a/frontend/system/src/Entities/api/operations.graphql +++ b/frontend/system/src/Entities/api/operations.graphql @@ -3,6 +3,7 @@ fragment EntityRow on EntityType { type description code + name alternativeNames { name } From a955a09996807adfe4fb6c37ebd194214a1ee33d Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 13:27:43 +1300 Subject: [PATCH 12/38] create generic delete lines dropdown item --- .../toolbars/DeleteLinesDropdownItem.tsx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 frontend/common/src/ui/components/toolbars/DeleteLinesDropdownItem.tsx diff --git a/frontend/common/src/ui/components/toolbars/DeleteLinesDropdownItem.tsx b/frontend/common/src/ui/components/toolbars/DeleteLinesDropdownItem.tsx new file mode 100644 index 000000000..6efa1f1fd --- /dev/null +++ b/frontend/common/src/ui/components/toolbars/DeleteLinesDropdownItem.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { useNotification } from '@common/hooks'; +import { useTranslation } from '@common/intl'; +import { DeleteIcon, DropdownMenuItem } from '@common/ui'; +import { + LocalStorage, + RecordWithId, + useConfirmationModal, +} from 'frontend/common/src'; + +export const DeleteLinesDropdownItem = ({ + selectedRows, + deleteItem, +}: { + selectedRows: (T | undefined)[]; + deleteItem: (item: T) => Promise; +}) => { + const t = useTranslation(); + const { success, info, error } = useNotification(); + + const deleteAction = () => { + if (selectedRows.length) { + let errMessage = ''; + Promise.all( + selectedRows.map(async row => { + if (!row) return; + await deleteItem(row).catch(err => { + if (!errMessage) errMessage = err.message; + }); + }) + ).then(() => { + // Separate check for authorisation error, as this is handled globally i.e. not caught above + // Not using useLocalStorage here, as hook result only updates on re-render (after this function finishes running!) + const authError = LocalStorage.getItem('/auth/error'); + if (!errMessage && !authError) { + const deletedMessage = t('messages.deleted-generic', { + count: selectedRows.length, + }); + success(deletedMessage)(); + } else { + error(errMessage ?? 'Unknown/Auth Error')(); + } + }); + } else { + info(t('messages.select-rows-to-delete'))(); + } + }; + + const showDeleteConfirmation = useConfirmationModal({ + onConfirm: deleteAction, + message: t('messages.confirm-delete-generic', { + count: selectedRows.length, + }), + title: t('heading.are-you-sure'), + }); + + return ( + showDeleteConfirmation()} + > + {t('button.delete-lines')} + + ); +}; From 796efb3000bbb24822940f95cecf2bb90fb48165 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 13:27:54 +1300 Subject: [PATCH 13/38] delete gs1 ui --- .../src/ui/components/toolbars/index.ts | 1 + .../src/Admin/GS1Barcodes/GS1ListView.tsx | 109 +++++++++++------- .../src/Admin/GS1Barcodes/api/hooks/index.ts | 1 + .../GS1Barcodes/api/hooks/useDeleteGS1.ts | 13 +++ .../GS1Barcodes/api/operations.generated.ts | 15 +++ .../Admin/GS1Barcodes/api/operations.graphql | 4 + 6 files changed, 103 insertions(+), 40 deletions(-) create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts diff --git a/frontend/common/src/ui/components/toolbars/index.ts b/frontend/common/src/ui/components/toolbars/index.ts index 971905bba..a55ded3c4 100644 --- a/frontend/common/src/ui/components/toolbars/index.ts +++ b/frontend/common/src/ui/components/toolbars/index.ts @@ -1,2 +1,3 @@ export * from './SearchAndDeleteToolbar'; export * from './SearchToolbar'; +export * from './DeleteLinesDropdownItem'; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx index ffd1af6bf..54484f864 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -2,8 +2,11 @@ import { useEditModal } from '@common/hooks'; import { useTranslation } from '@common/intl'; import { AppBarButtonsPortal, + AppBarContentPortal, createTableStore, DataTable, + DeleteLinesDropdownItem, + DropdownMenu, LoadingButton, NothingHere, PlusCircleIcon, @@ -11,61 +14,68 @@ import { Tooltip, Typography, useColumns, + useTableStore, } from '@common/ui'; import React from 'react'; -import { useGS1Barcodes } from './api'; +import { useDeleteGS1, useGS1Barcodes } from './api'; import { Gs1Fragment } from './api/operations.generated'; import { GS1EditModal } from './GS1EditModal'; -export const GS1ListView = () => { +const removeName = (description: string, name: string) => { + const nameIndex = description.lastIndexOf(name); + if (nameIndex === -1) return description; + return description.substring(0, nameIndex); +}; + +const GS1ListViewComponent = () => { const t = useTranslation('system'); const { onOpen, onClose, isOpen } = useEditModal(); - const removeName = (description: string, name: string) => { - const nameIndex = description.indexOf(name); - if (nameIndex === -1) return description; - return description.substring(0, nameIndex); - }; + const { mutateAsync: deleteGS1 } = useDeleteGS1(); + const { data, isError, isLoading } = useGS1Barcodes(); - const columns = useColumns( - [ - { - key: 'entity', - label: 'label.product', - Cell: ({ rowData }) => { - const description = removeName( - rowData.entity.description, - rowData.entity.name - ); - return ( - - - {description.length > 35 - ? description.substring(0, 35) + '...' - : description} - - - ); - }, - }, - { - key: 'also entity', // TODO does this have implications... - label: 'label.pack-size', - Cell: ({ rowData }) => <>{rowData.entity.name}, - }, - { key: 'manufacturer', label: 'label.manufacturer' }, - { key: 'id', label: 'label.gtin' }, - ], - {}, - [] + const selectedRows = useTableStore(state => + Object.keys(state.rowState) + .filter(id => state.rowState[id]?.isSelected) + .map(selectedId => data?.find(({ id }) => selectedId === id)) + .filter(Boolean) ); - const { data, isError, isLoading } = useGS1Barcodes(); + const columns = useColumns([ + { + key: 'entity', + label: 'label.product', + Cell: ({ rowData }) => { + const description = removeName( + rowData.entity.description, + rowData.entity.name + ); + return ( + + + {description.length > 35 + ? description.substring(0, 35) + '...' + : description} + + + ); + }, + }, + { + key: 'entity2', // also on entity, but we need to use different key to avoid error + label: 'label.pack-size', + Cell: ({ rowData }) => <>{rowData.entity.name}, + }, + { key: 'manufacturer', label: 'label.manufacturer' }, + { key: 'id', label: 'label.gtin' }, + 'selection', + ]); return ( - + <> {isOpen && } + onOpen()} @@ -76,6 +86,17 @@ export const GS1ListView = () => { + + + { + await deleteGS1({ gtin: item.gtin }); + }} + /> + + + { // pagination={false} // TODO // onChangePage={updatePaginationQuery} /> + + ); +}; + +export const GS1ListView = () => { + return ( + + ); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts index 91873ac4f..433798f41 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts @@ -1,2 +1,3 @@ export * from './useGS1Barcodes'; export * from './useAddGS1'; +export * from './useDeleteGS1'; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts new file mode 100644 index 000000000..f7650d79c --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts @@ -0,0 +1,13 @@ +import { useGql, useMutation, useQueryClient } from '@uc-frontend/common'; +import { GS1_BARCODES_KEY } from 'frontend/system/src/queryKeys'; +import { getSdk } from '../operations.generated'; + +export const useDeleteGS1 = () => { + const { client } = useGql(); + const sdk = getSdk(client); + const queryClient = useQueryClient(); + + return useMutation(sdk.DeleteGS1, { + onSettled: () => queryClient.invalidateQueries(GS1_BARCODES_KEY), + }); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts index 56eaa2d2f..d69abf3c6 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts @@ -17,6 +17,13 @@ export type AddGs1MutationVariables = Types.Exact<{ export type AddGs1Mutation = { __typename?: 'FullMutation', addGs1: { __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } } }; +export type DeleteGs1MutationVariables = Types.Exact<{ + gtin: Types.Scalars['String']['input']; +}>; + + +export type DeleteGs1Mutation = { __typename?: 'FullMutation', deleteGs1: number }; + export const Gs1FragmentDoc = gql` fragment GS1 on Gs1Node { id @@ -48,6 +55,11 @@ export const AddGs1Document = gql` } } ${Gs1FragmentDoc}`; +export const DeleteGs1Document = gql` + mutation DeleteGS1($gtin: String!) { + deleteGs1(gtin: $gtin) +} + `; export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; @@ -61,6 +73,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = }, AddGs1(variables: AddGs1MutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(AddGs1Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddGs1', 'mutation'); + }, + DeleteGS1(variables: DeleteGs1MutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(DeleteGs1Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'DeleteGS1', 'mutation'); } }; } diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql index aeca4d075..b306133e7 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql @@ -25,3 +25,7 @@ mutation AddGs1($input: AddGS1Input!) { ...GS1 } } + +mutation DeleteGS1($gtin: String!) { + deleteGs1(gtin: $gtin) +} From 201e5b8aeb1fadff5a6dddc8e6b8d306ceeea8bb Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 13:43:41 +1300 Subject: [PATCH 14/38] refactor getParentDescription to helpers --- .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 8 ++---- .../src/Admin/GS1Barcodes/GS1ListView.tsx | 12 ++------- .../src/Admin/GS1Barcodes/helpers.test.ts | 27 +++++++++++++++++++ .../system/src/Admin/GS1Barcodes/helpers.ts | 11 ++++++++ 4 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 frontend/system/src/Admin/GS1Barcodes/helpers.test.ts create mode 100644 frontend/system/src/Admin/GS1Barcodes/helpers.ts diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx index b0293d1a1..30db51b70 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -17,6 +17,7 @@ import React, { useState } from 'react'; import { useEntities } from '../../Entities/api'; import { EntityRowFragment } from '../../Entities/api/operations.generated'; import { useAddGS1 } from './api'; +import { getParentDescription } from './helpers'; type GS1EditModalProps = { isOpen: boolean; @@ -145,11 +146,6 @@ export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { ); }; -const getParentDescription = (description: string, name: string) => { - const nameIndex = description.lastIndexOf(name); - if (nameIndex === -1) return description; - return description.substring(0, nameIndex); -}; const renderOption: AutocompleteOptionRenderer = ( props, @@ -160,7 +156,7 @@ const renderOption: AutocompleteOptionRenderer = ( {option.code} - {getParentDescription(option.description, option.name)} + {getParentDescription(option)} {option.name} diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx index 54484f864..c10a31388 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -20,12 +20,7 @@ import React from 'react'; import { useDeleteGS1, useGS1Barcodes } from './api'; import { Gs1Fragment } from './api/operations.generated'; import { GS1EditModal } from './GS1EditModal'; - -const removeName = (description: string, name: string) => { - const nameIndex = description.lastIndexOf(name); - if (nameIndex === -1) return description; - return description.substring(0, nameIndex); -}; +import { getParentDescription } from './helpers'; const GS1ListViewComponent = () => { const t = useTranslation('system'); @@ -47,10 +42,7 @@ const GS1ListViewComponent = () => { key: 'entity', label: 'label.product', Cell: ({ rowData }) => { - const description = removeName( - rowData.entity.description, - rowData.entity.name - ); + const description = getParentDescription(rowData.entity); return ( diff --git a/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts b/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts new file mode 100644 index 000000000..87e03982d --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts @@ -0,0 +1,27 @@ +import { getParentDescription } from './helpers'; + +describe('getParentDescription', () => { + it('returns full description when name doesnt match', () => { + expect( + getParentDescription({ description: 'some description', name: 'name' }) + ).toEqual('some description'); + }); + + it('returns description with name removed', () => { + expect( + getParentDescription({ + description: 'some description name', + name: 'name', + }) + ).toEqual('some description'); + }); + + it('returns description correctly when name is also in the middle', () => { + expect( + getParentDescription({ + description: 'description name bla name', + name: 'name', + }) + ).toEqual('description name bla'); + }); +}); diff --git a/frontend/system/src/Admin/GS1Barcodes/helpers.ts b/frontend/system/src/Admin/GS1Barcodes/helpers.ts new file mode 100644 index 000000000..1c6b8ca28 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/helpers.ts @@ -0,0 +1,11 @@ +export const getParentDescription = ({ + description, + name, +}: { + description: string; + name: string; +}) => { + const nameIndex = description.lastIndexOf(name); + if (nameIndex === -1) return description; + return description.substring(0, nameIndex).trim(); +}; From 15fcb06f2a70c2f3451263b3f722ae90bac1b0bb Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 14:15:15 +1300 Subject: [PATCH 15/38] add pagination --- backend/dgraph/src/gs1/gs1s.rs | 21 ++++++++++++---- backend/graphql/gs1/src/lib.rs | 11 +++++---- backend/service/src/gs1/mod.rs | 14 ++++++++--- frontend/common/src/types/schema.ts | 6 +++++ .../src/Admin/GS1Barcodes/GS1ListView.tsx | 24 ++++++++++++++----- .../GS1Barcodes/api/hooks/useGS1Barcodes.ts | 15 ++++++++---- .../GS1Barcodes/api/operations.generated.ts | 9 ++++--- .../Admin/GS1Barcodes/api/operations.graphql | 4 ++-- 8 files changed, 78 insertions(+), 26 deletions(-) diff --git a/backend/dgraph/src/gs1/gs1s.rs b/backend/dgraph/src/gs1/gs1s.rs index 6e06ebfa6..fb27efc89 100644 --- a/backend/dgraph/src/gs1/gs1s.rs +++ b/backend/dgraph/src/gs1/gs1s.rs @@ -1,11 +1,21 @@ use gql_client::GraphQLError; +use serde::Serialize; use crate::{DgraphClient, GS1Data}; -pub async fn gs1s(client: &DgraphClient) -> Result, GraphQLError> { +#[derive(Serialize, Debug)] +pub struct GS1QueryVars { + pub first: Option, + pub offset: Option, +} + +pub async fn gs1s( + client: &DgraphClient, + vars: GS1QueryVars, +) -> Result, GraphQLError> { let query = r#" -query gs1s { - data: queryGS1 { +query gs1s($first: Int, $offset: Int) { + data: queryGS1(first: $first, offset: $offset) { manufacturer gtin entity { @@ -16,7 +26,10 @@ query gs1s { } } "#; - let data = client.gql.query::(query).await?; + let data = client + .gql + .query_with_vars::(query, vars) + .await?; Ok(data) } diff --git a/backend/graphql/gs1/src/lib.rs b/backend/graphql/gs1/src/lib.rs index 1b1bc713d..1b984fcd7 100644 --- a/backend/graphql/gs1/src/lib.rs +++ b/backend/graphql/gs1/src/lib.rs @@ -15,11 +15,14 @@ impl GS1Queries { pub async fn gs1_barcodes( &self, ctx: &Context<'_>, - // order_by: Option, - // first: Option, - // offset: Option, + first: Option, + offset: Option, ) -> Result { - let result = ctx.service_provider().gs1_service.gs1s().await?; + let result = ctx + .service_provider() + .gs1_service + .gs1s(first, offset) + .await?; Ok(GS1CollectionResponse::Response( GS1CollectionConnector::from_domain(result), diff --git a/backend/service/src/gs1/mod.rs b/backend/service/src/gs1/mod.rs index 11d04bdfc..e7998a377 100644 --- a/backend/service/src/gs1/mod.rs +++ b/backend/service/src/gs1/mod.rs @@ -3,7 +3,11 @@ use std::{ sync::Arc, }; -use dgraph::{gs1::gs1::gs1_by_gtin, gs1s::gs1s, DgraphClient, GraphQLError, GS1}; +use dgraph::{ + gs1::gs1::gs1_by_gtin, + gs1s::{gs1s, GS1QueryVars}, + DgraphClient, GraphQLError, GS1, +}; use repository::RepositoryError; use util::usize_to_u32; @@ -77,8 +81,12 @@ impl GS1Service { } } - pub async fn gs1s(&self) -> Result { - let result = gs1s(&self.client) + pub async fn gs1s( + &self, + first: Option, + offset: Option, + ) -> Result { + let result = gs1s(&self.client, GS1QueryVars { first, offset }) .await .map_err(|e| GS1ServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? diff --git a/frontend/common/src/types/schema.ts b/frontend/common/src/types/schema.ts index 2fb8ce3d3..601be0a2b 100644 --- a/frontend/common/src/types/schema.ts +++ b/frontend/common/src/types/schema.ts @@ -385,6 +385,12 @@ export type FullQueryEntityArgs = { }; +export type FullQueryGs1BarcodesArgs = { + first?: InputMaybe; + offset?: InputMaybe; +}; + + export type FullQueryLogsArgs = { filter?: InputMaybe; page?: InputMaybe; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx index c10a31388..fd1d6309c 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -1,4 +1,4 @@ -import { useEditModal } from '@common/hooks'; +import { useEditModal, useQueryParamsState } from '@common/hooks'; import { useTranslation } from '@common/intl'; import { AppBarButtonsPortal, @@ -27,13 +27,17 @@ const GS1ListViewComponent = () => { const { onOpen, onClose, isOpen } = useEditModal(); + const { queryParams, updatePaginationQuery } = useQueryParamsState(); + + const { data, isError, isLoading } = useGS1Barcodes(queryParams); const { mutateAsync: deleteGS1 } = useDeleteGS1(); - const { data, isError, isLoading } = useGS1Barcodes(); + + const gs1Barcodes = data?.data ?? []; const selectedRows = useTableStore(state => Object.keys(state.rowState) .filter(id => state.rowState[id]?.isSelected) - .map(selectedId => data?.find(({ id }) => selectedId === id)) + .map(selectedId => gs1Barcodes.find(({ id }) => selectedId === id)) .filter(Boolean) ); @@ -64,6 +68,14 @@ const GS1ListViewComponent = () => { 'selection', ]); + const { page, first, offset } = queryParams; + const pagination = { + page, + offset, + first, + total: data?.totalCount, + }; + return ( <> {isOpen && } @@ -91,12 +103,12 @@ const GS1ListViewComponent = () => { } - // pagination={false} // TODO - // onChangePage={updatePaginationQuery} + pagination={pagination} + onChangePage={updatePaginationQuery} /> ); diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts index 0ee3d4c90..00e589685 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts @@ -2,12 +2,19 @@ import { useGql, useQuery } from 'frontend/common/src'; import { GS1_BARCODES_KEY } from '../../../../queryKeys'; import { getSdk } from '../operations.generated'; -export const useGS1Barcodes = () => { +export const useGS1Barcodes = ({ + first, + offset, +}: { + first: number; + offset: number; +}) => { const { client } = useGql(); const sdk = getSdk(client); - const cacheKeys = [GS1_BARCODES_KEY]; + const cacheKeys = [GS1_BARCODES_KEY, first, offset]; + return useQuery(cacheKeys, async () => { - const response = await sdk.Gs1Barcodes(); - return response?.gs1Barcodes?.data ?? []; + const response = await sdk.Gs1Barcodes({ first, offset }); + return response?.gs1Barcodes; }); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts index d69abf3c6..4f2b89741 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts @@ -5,7 +5,10 @@ import * as Dom from 'graphql-request/dist/types.dom'; import gql from 'graphql-tag'; export type Gs1Fragment = { __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }; -export type Gs1BarcodesQueryVariables = Types.Exact<{ [key: string]: never; }>; +export type Gs1BarcodesQueryVariables = Types.Exact<{ + first?: Types.InputMaybe; + offset?: Types.InputMaybe; +}>; export type Gs1BarcodesQuery = { __typename?: 'FullQuery', gs1Barcodes: { __typename?: 'Gs1CollectionConnector', totalCount: number, data: Array<{ __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }> } }; @@ -37,8 +40,8 @@ export const Gs1FragmentDoc = gql` } `; export const Gs1BarcodesDocument = gql` - query Gs1Barcodes { - gs1Barcodes { + query Gs1Barcodes($first: Int, $offset: Int) { + gs1Barcodes(first: $first, offset: $offset) { ... on Gs1CollectionConnector { data { ...GS1 diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql index b306133e7..f74e4f137 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql @@ -9,8 +9,8 @@ fragment GS1 on Gs1Node { } } -query Gs1Barcodes { - gs1Barcodes { +query Gs1Barcodes($first: Int, $offset: Int) { + gs1Barcodes(first: $first, offset: $offset) { ... on Gs1CollectionConnector { data { ...GS1 From ce0972455b839a4bd45765a5f4a064f8a1ba738f Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 14:29:32 +1300 Subject: [PATCH 16/38] extract gs1 list from list view --- .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 2 +- .../system/src/Admin/GS1Barcodes/GS1List.tsx | 125 ++++++++++++++++++ .../src/Admin/GS1Barcodes/GS1ListView.tsx | 115 ++-------------- 3 files changed, 137 insertions(+), 105 deletions(-) create mode 100644 frontend/system/src/Admin/GS1Barcodes/GS1List.tsx diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx index 30db51b70..d31dced6c 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -156,7 +156,7 @@ const renderOption: AutocompleteOptionRenderer = ( {option.code} - {getParentDescription(option)} + {getParentDescription(option)}{' '} {option.name} diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx new file mode 100644 index 000000000..9c7116239 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx @@ -0,0 +1,125 @@ +import { useEditModal } from '@common/hooks'; +import { useTranslation } from '@common/intl'; +import { + AppBarButtonsPortal, + AppBarContentPortal, + createTableStore, + DataTable, + DeleteLinesDropdownItem, + DropdownMenu, + LoadingButton, + NothingHere, + PlusCircleIcon, + TableProps, + TableProvider, + Tooltip, + Typography, + useColumns, + useTableStore, +} from '@common/ui'; +import React from 'react'; +import { useDeleteGS1 } from './api'; +import { Gs1Fragment } from './api/operations.generated'; +import { GS1EditModal } from './GS1EditModal'; +import { getParentDescription } from './helpers'; + +interface GS1ListProps { + gs1Barcodes: Gs1Fragment[]; + isError: boolean; + isLoading: boolean; + pagination?: TableProps['pagination']; + updatePaginationQuery?: (page: number) => void; +} + +const GS1ListComponent = ({ + gs1Barcodes, + isError, + isLoading, + pagination, + updatePaginationQuery, +}: GS1ListProps) => { + const t = useTranslation('system'); + + const { onOpen, onClose, isOpen } = useEditModal(); + + const { mutateAsync: deleteGS1 } = useDeleteGS1(); + + const selectedRows = useTableStore(state => + Object.keys(state.rowState) + .filter(id => state.rowState[id]?.isSelected) + .map(selectedId => gs1Barcodes.find(({ id }) => selectedId === id)) + .filter(Boolean) + ); + + const columns = useColumns([ + { + key: 'entity', + label: 'label.product', + Cell: ({ rowData }) => { + const description = getParentDescription(rowData.entity); + return ( + + + {description.length > 35 + ? description.substring(0, 35) + '...' + : description} + + + ); + }, + }, + { + key: 'entity2', // also on entity, but we need to use different key to avoid error + label: 'label.pack-size', + Cell: ({ rowData }) => <>{rowData.entity.name}, + }, + { key: 'manufacturer', label: 'label.manufacturer' }, + { key: 'id', label: 'label.gtin' }, + 'selection', + ]); + + return ( + <> + {isOpen && } + + + onOpen()} + isLoading={false} + startIcon={} + > + {t('label.add-barcode')} + + + + + + { + await deleteGS1({ gtin: item.gtin }); + }} + /> + + + + } + pagination={pagination} + onChangePage={updatePaginationQuery} + /> + + ); +}; + +export const GS1List = (props: GS1ListProps) => { + return ( + + + + ); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx index fd1d6309c..ca29e0106 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListView.tsx @@ -1,73 +1,15 @@ -import { useEditModal, useQueryParamsState } from '@common/hooks'; -import { useTranslation } from '@common/intl'; -import { - AppBarButtonsPortal, - AppBarContentPortal, - createTableStore, - DataTable, - DeleteLinesDropdownItem, - DropdownMenu, - LoadingButton, - NothingHere, - PlusCircleIcon, - TableProvider, - Tooltip, - Typography, - useColumns, - useTableStore, -} from '@common/ui'; +import { useQueryParamsState } from '@common/hooks'; import React from 'react'; -import { useDeleteGS1, useGS1Barcodes } from './api'; -import { Gs1Fragment } from './api/operations.generated'; -import { GS1EditModal } from './GS1EditModal'; -import { getParentDescription } from './helpers'; - -const GS1ListViewComponent = () => { - const t = useTranslation('system'); - - const { onOpen, onClose, isOpen } = useEditModal(); +import { useGS1Barcodes } from './api'; +import { GS1List } from './GS1List'; +export const GS1ListView = () => { const { queryParams, updatePaginationQuery } = useQueryParamsState(); const { data, isError, isLoading } = useGS1Barcodes(queryParams); - const { mutateAsync: deleteGS1 } = useDeleteGS1(); const gs1Barcodes = data?.data ?? []; - const selectedRows = useTableStore(state => - Object.keys(state.rowState) - .filter(id => state.rowState[id]?.isSelected) - .map(selectedId => gs1Barcodes.find(({ id }) => selectedId === id)) - .filter(Boolean) - ); - - const columns = useColumns([ - { - key: 'entity', - label: 'label.product', - Cell: ({ rowData }) => { - const description = getParentDescription(rowData.entity); - return ( - - - {description.length > 35 - ? description.substring(0, 35) + '...' - : description} - - - ); - }, - }, - { - key: 'entity2', // also on entity, but we need to use different key to avoid error - label: 'label.pack-size', - Cell: ({ rowData }) => <>{rowData.entity.name}, - }, - { key: 'manufacturer', label: 'label.manufacturer' }, - { key: 'id', label: 'label.gtin' }, - 'selection', - ]); - const { page, first, offset } = queryParams; const pagination = { page, @@ -77,47 +19,12 @@ const GS1ListViewComponent = () => { }; return ( - <> - {isOpen && } - - - onOpen()} - isLoading={false} - startIcon={} - > - {t('label.add-barcode')} - - - - - - { - await deleteGS1({ gtin: item.gtin }); - }} - /> - - - - } - pagination={pagination} - onChangePage={updatePaginationQuery} - /> - - ); -}; - -export const GS1ListView = () => { - return ( - - - + ); }; From 49a12e9c16e71047867da12414ef8581c15a69aa Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 14:35:10 +1300 Subject: [PATCH 17/38] show pack size code in table --- frontend/system/src/Admin/GS1Barcodes/GS1List.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx index 9c7116239..b131d2f89 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx @@ -71,7 +71,11 @@ const GS1ListComponent = ({ { key: 'entity2', // also on entity, but we need to use different key to avoid error label: 'label.pack-size', - Cell: ({ rowData }) => <>{rowData.entity.name}, + Cell: ({ rowData }) => ( + <> + {rowData.entity.name} ({rowData.entity.code}) + + ), }, { key: 'manufacturer', label: 'label.manufacturer' }, { key: 'id', label: 'label.gtin' }, From 1bc851046bf2451fbec1b912ffc1ac7cf49bef23 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 15:29:26 +1300 Subject: [PATCH 18/38] view GS1s for a pack size --- backend/dgraph/src/entity.rs | 8 +++ backend/dgraph/src/lib.rs | 8 +++ .../universal_codes/src/types/entity.rs | 7 ++ .../universal_codes/src/types/gs1.rs | 35 ++++++++++ .../universal_codes/src/types/mod.rs | 2 + frontend/common/src/types/schema.ts | 8 +++ .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 70 +++++++++++-------- .../system/src/Admin/GS1Barcodes/GS1List.tsx | 12 +++- .../GS1Barcodes/GS1ListForEntityView.tsx | 37 ++++++++++ .../src/Admin/GS1Barcodes/api/hooks/index.ts | 1 + .../Admin/GS1Barcodes/api/hooks/useAddGS1.ts | 6 +- .../api/hooks/useEntityWithGS1s.ts | 15 ++++ .../GS1Barcodes/api/operations.generated.ts | 35 ++++++++++ .../Admin/GS1Barcodes/api/operations.graphql | 17 +++++ frontend/system/src/Admin/Service.tsx | 5 ++ frontend/system/src/queryKeys.ts | 1 + 16 files changed, 233 insertions(+), 34 deletions(-) create mode 100644 backend/graphql_v1/universal_codes/src/types/gs1.rs create mode 100644 frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx create mode 100644 frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts diff --git a/backend/dgraph/src/entity.rs b/backend/dgraph/src/entity.rs index d4e62ef71..100f7b591 100644 --- a/backend/dgraph/src/entity.rs +++ b/backend/dgraph/src/entity.rs @@ -18,6 +18,10 @@ pub async fn entity_by_code( name description alternative_names + gs1s { + gtin + manufacturer + } type __typename properties { @@ -87,6 +91,10 @@ pub async fn entity_with_parents_by_code( code name description + gs1s { + gtin + manufacturer + } alternative_names type __typename diff --git a/backend/dgraph/src/lib.rs b/backend/dgraph/src/lib.rs index 34fcb88b7..6942f0fdf 100644 --- a/backend/dgraph/src/lib.rs +++ b/backend/dgraph/src/lib.rs @@ -76,6 +76,8 @@ pub struct Entity { #[serde(default)] pub alternative_names: Option, #[serde(default)] + pub gs1s: Vec, + #[serde(default)] pub properties: Vec, #[serde(default)] pub children: Vec, @@ -93,6 +95,12 @@ pub struct Property { pub value: String, } +#[derive(Deserialize, Debug, Clone)] +pub struct GS1Info { + pub gtin: String, + pub manufacturer: String, +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] pub enum ChangeType { Change, diff --git a/backend/graphql_v1/universal_codes/src/types/entity.rs b/backend/graphql_v1/universal_codes/src/types/entity.rs index d9ff9ea9f..54c30f9fb 100644 --- a/backend/graphql_v1/universal_codes/src/types/entity.rs +++ b/backend/graphql_v1/universal_codes/src/types/entity.rs @@ -2,6 +2,7 @@ use async_graphql::*; use dgraph::Entity; use crate::AlternativeNameType; +use crate::GS1Type; use super::DrugInteractionType; use super::PropertiesType; @@ -15,6 +16,7 @@ pub struct EntityType { pub r#type: String, pub category: String, pub alternative_names: Vec, + pub gs1s: Vec, pub properties: Vec, pub children: Vec, pub parents: Vec, @@ -29,6 +31,7 @@ impl EntityType { description: entity.description, r#type: entity.r#type, category: entity.category, + gs1s: GS1Type::from_domain(entity.gs1s), properties: PropertiesType::from_domain(entity.properties), alternative_names: match entity.alternative_names { Some(names) => AlternativeNameType::from_domain(names), @@ -68,6 +71,10 @@ impl EntityType { get_type_for_entity(&self) } + pub async fn gs1_barcodes(&self) -> &Vec { + &self.gs1s + } + pub async fn properties(&self) -> &Vec { &self.properties } diff --git a/backend/graphql_v1/universal_codes/src/types/gs1.rs b/backend/graphql_v1/universal_codes/src/types/gs1.rs new file mode 100644 index 000000000..9f72c5fb0 --- /dev/null +++ b/backend/graphql_v1/universal_codes/src/types/gs1.rs @@ -0,0 +1,35 @@ +use async_graphql::*; +use dgraph::GS1Info; + +#[derive(Clone, Debug)] +pub struct GS1Type { + pub manufacturer: String, + pub gtin: String, +} + +#[Object] +impl GS1Type { + pub async fn id(&self) -> &str { + &self.gtin + } + + pub async fn manufacturer(&self) -> &str { + &self.manufacturer + } + + pub async fn gtin(&self) -> &str { + &self.gtin + } +} + +impl GS1Type { + pub fn from_domain(entity_gs1s: Vec) -> Vec { + entity_gs1s + .into_iter() + .map(|g| GS1Type { + gtin: g.gtin, + manufacturer: g.manufacturer, + }) + .collect() + } +} diff --git a/backend/graphql_v1/universal_codes/src/types/mod.rs b/backend/graphql_v1/universal_codes/src/types/mod.rs index 031e3ba2c..ba3d4b99c 100644 --- a/backend/graphql_v1/universal_codes/src/types/mod.rs +++ b/backend/graphql_v1/universal_codes/src/types/mod.rs @@ -10,3 +10,5 @@ mod entity_search_input; pub use entity_search_input::*; mod entity_sort; pub use entity_sort::*; +mod gs1; +pub use gs1::*; diff --git a/frontend/common/src/types/schema.ts b/frontend/common/src/types/schema.ts index 601be0a2b..1758c5eb2 100644 --- a/frontend/common/src/types/schema.ts +++ b/frontend/common/src/types/schema.ts @@ -176,6 +176,7 @@ export type EntityType = { children: Array; code: Scalars['String']['output']; description: Scalars['String']['output']; + gs1Barcodes: Array; interactions?: Maybe>; name: Scalars['String']['output']; parents: Array; @@ -438,6 +439,13 @@ export type Gs1Node = { export type Gs1Response = Gs1Node; +export type Gs1Type = { + __typename: 'Gs1Type'; + gtin: Scalars['String']['output']; + id: Scalars['String']['output']; + manufacturer: Scalars['String']['output']; +}; + export type IdResponse = { __typename: 'IdResponse'; id: Scalars['String']['output']; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx index d31dced6c..236f809df 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -22,14 +22,19 @@ import { getParentDescription } from './helpers'; type GS1EditModalProps = { isOpen: boolean; onClose: () => void; + entityCode?: string; }; -export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { +export const GS1EditModal = ({ + isOpen, + onClose, + entityCode, +}: GS1EditModalProps) => { const t = useTranslation('system'); const [errorMessage, setErrorMessage] = useState(null); const [draft, setDraft] = useState({ - entityCode: '', + entityCode: entityCode ?? '', gtin: '', manufacturer: '', }); @@ -99,35 +104,38 @@ export const GS1EditModal = ({ isOpen, onClose }: GS1EditModalProps) => { value={draft.manufacturer} onChange={e => setDraft({ ...draft, manufacturer: e.target.value })} /> - `${option.id}`} - width={modalWidth - 50} - openOnFocus - renderInput={props => ( - - )} - filterOptions={(options, state) => - options.filter(option => - RegexUtils.matchObjectProperties(state.inputValue, option, [ - 'description', - 'code', - ]) - ) - } - onChange={(e, value) => - setDraft({ - ...draft, - entityCode: (value as unknown as EntityRowFragment)?.code ?? '', - }) - } - /> + {entityCode ? null : ( + `${option.id}`} + width={modalWidth - 50} + openOnFocus + renderInput={props => ( + + )} + filterOptions={(options, state) => + options.filter(option => + RegexUtils.matchObjectProperties(state.inputValue, option, [ + 'description', + 'code', + ]) + ) + } + onChange={(e, value) => + setDraft({ + ...draft, + entityCode: + (value as unknown as EntityRowFragment)?.code ?? '', + }) + } + /> + )} {errorMessage ? ( []; isError: boolean; isLoading: boolean; + entityCode?: string; pagination?: TableProps['pagination']; updatePaginationQuery?: (page: number) => void; } @@ -35,6 +36,7 @@ const GS1ListComponent = ({ gs1Barcodes, isError, isLoading, + entityCode, pagination, updatePaginationQuery, }: GS1ListProps) => { @@ -84,7 +86,13 @@ const GS1ListComponent = ({ return ( <> - {isOpen && } + {isOpen && ( + + )} { + const t = useTranslation('system'); + const { code } = useParams(); + const { setSuffix } = useBreadcrumbs(); + + const { data: entity, isError, isLoading } = useEntityWithGS1s(code ?? ''); + + useEffect(() => { + setSuffix(t('label.details')); + }, []); + + const gs1Barcodes = entity?.gs1Barcodes.map(b => ({ ...b, entity })) ?? []; + + return ( + <> + + + {entity?.description} + + + + + ); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts index 433798f41..4c6bc5065 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useGS1Barcodes'; export * from './useAddGS1'; export * from './useDeleteGS1'; +export * from './useEntityWithGS1s'; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useAddGS1.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useAddGS1.ts index 598cd725a..8744035c7 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useAddGS1.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useAddGS1.ts @@ -1,5 +1,8 @@ import { useQueryClient, useGql, useMutation } from '@uc-frontend/common'; -import { GS1_BARCODES_KEY } from 'frontend/system/src/queryKeys'; +import { + ENTITY_WITH_GS1S_KEY, + GS1_BARCODES_KEY, +} from 'frontend/system/src/queryKeys'; import { getSdk } from '../operations.generated'; export const useAddGS1 = () => { @@ -9,6 +12,7 @@ export const useAddGS1 = () => { const invalidateQueries = () => { queryClient.invalidateQueries([GS1_BARCODES_KEY]); + queryClient.invalidateQueries([ENTITY_WITH_GS1S_KEY]); }; return useMutation(sdk.AddGs1, { diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts new file mode 100644 index 000000000..80d98fd95 --- /dev/null +++ b/frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts @@ -0,0 +1,15 @@ +import { useGql, useQuery } from '@uc-frontend/common'; +import { ENTITY_WITH_GS1S_KEY } from 'frontend/system/src/queryKeys'; +import { getSdk } from '../operations.generated'; + +export const useEntityWithGS1s = (code: string) => { + const { client } = useGql(); + const sdk = getSdk(client); + + const cacheKeys = [ENTITY_WITH_GS1S_KEY, code]; + + return useQuery(cacheKeys, async () => { + const response = await sdk.entityWithGS1s({ code }); + return response?.entity; + }); +}; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts index 4f2b89741..47f864206 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts @@ -27,6 +27,15 @@ export type DeleteGs1MutationVariables = Types.Exact<{ export type DeleteGs1Mutation = { __typename?: 'FullMutation', deleteGs1: number }; +export type EntityWithGs1sFragment = { __typename?: 'EntityType', code: string, name: string, description: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }> }; + +export type EntityWithGs1sQueryVariables = Types.Exact<{ + code: Types.Scalars['String']['input']; +}>; + + +export type EntityWithGs1sQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, description: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }> } | null }; + export const Gs1FragmentDoc = gql` fragment GS1 on Gs1Node { id @@ -39,6 +48,22 @@ export const Gs1FragmentDoc = gql` } } `; +export const EntityWithGs1sFragmentDoc = gql` + fragment EntityWithGS1s on EntityType { + code + name + description + gs1Barcodes { + id + gtin + manufacturer + } + alternativeNames { + code + name + } +} + `; export const Gs1BarcodesDocument = gql` query Gs1Barcodes($first: Int, $offset: Int) { gs1Barcodes(first: $first, offset: $offset) { @@ -63,6 +88,13 @@ export const DeleteGs1Document = gql` deleteGs1(gtin: $gtin) } `; +export const EntityWithGs1sDocument = gql` + query entityWithGS1s($code: String!) { + entity(code: $code) { + ...EntityWithGS1s + } +} + ${EntityWithGs1sFragmentDoc}`; export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; @@ -79,6 +111,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = }, DeleteGS1(variables: DeleteGs1MutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(DeleteGs1Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'DeleteGS1', 'mutation'); + }, + entityWithGS1s(variables: EntityWithGs1sQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(EntityWithGs1sDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'entityWithGS1s', 'query'); } }; } diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql index f74e4f137..dd20cd6e2 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql @@ -29,3 +29,20 @@ mutation AddGs1($input: AddGS1Input!) { mutation DeleteGS1($gtin: String!) { deleteGs1(gtin: $gtin) } + +fragment EntityWithGS1s on EntityType { + code + name + description + gs1Barcodes { + id + gtin + manufacturer + } +} + +query entityWithGS1s($code: String!) { + entity(code: $code) { + ...EntityWithGS1s + } +} diff --git a/frontend/system/src/Admin/Service.tsx b/frontend/system/src/Admin/Service.tsx index 44a52fe69..48e38ac89 100644 --- a/frontend/system/src/Admin/Service.tsx +++ b/frontend/system/src/Admin/Service.tsx @@ -7,6 +7,7 @@ import { ConfigurationTabsView } from './Configuration'; import { DrugInteractionsView } from './Interactions'; import { PendingChangeDetails, PendingChangesListView } from './PendingChanges'; import { GS1ListView } from './GS1Barcodes'; +import { GS1ListForEntityView } from './GS1Barcodes/GS1ListForEntityView'; const AdminService = () => { return ( @@ -34,6 +35,10 @@ const AdminService = () => { element={} /> } /> + } + /> } diff --git a/frontend/system/src/queryKeys.ts b/frontend/system/src/queryKeys.ts index aee67d074..b506ee162 100644 --- a/frontend/system/src/queryKeys.ts +++ b/frontend/system/src/queryKeys.ts @@ -4,3 +4,4 @@ export const PENDING_CHANGE_KEY = 'PENDING_CHANGE'; export const PENDING_CHANGES_KEY = 'PENDING_CHANGES'; export const PROPERTY_CONFIG_ITEMS_KEY = 'PROPERTY_CONFIG_ITEMS'; export const GS1_BARCODES_KEY = 'GS1_BARCODES'; +export const ENTITY_WITH_GS1S_KEY = 'ENTITY_WITH_GS1S'; From 579b0edb3d21fc45d4b9aad96b397511d0815e04 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 19:20:26 +1300 Subject: [PATCH 19/38] display GS1 codes for entity from any level --- .../src/Admin/GS1Barcodes/GS1EditModal.tsx | 62 +++---- .../system/src/Admin/GS1Barcodes/GS1List.tsx | 10 +- .../GS1Barcodes/GS1ListForEntityView.tsx | 10 +- .../GS1Barcodes/api/operations.generated.ts | 15 +- .../Admin/GS1Barcodes/api/operations.graphql | 7 + .../src/Admin/GS1Barcodes/helpers.test.ts | 171 +++++++++++++++++- .../system/src/Admin/GS1Barcodes/helpers.ts | 53 ++++++ 7 files changed, 282 insertions(+), 46 deletions(-) diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx index 236f809df..ab2a4b570 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx @@ -22,19 +22,19 @@ import { getParentDescription } from './helpers'; type GS1EditModalProps = { isOpen: boolean; onClose: () => void; - entityCode?: string; + entityCodes?: string[]; }; export const GS1EditModal = ({ isOpen, onClose, - entityCode, + entityCodes, }: GS1EditModalProps) => { const t = useTranslation('system'); const [errorMessage, setErrorMessage] = useState(null); const [draft, setDraft] = useState({ - entityCode: entityCode ?? '', + entityCode: '', gtin: '', manufacturer: '', }); @@ -104,38 +104,38 @@ export const GS1EditModal = ({ value={draft.manufacturer} onChange={e => setDraft({ ...draft, manufacturer: e.target.value })} /> - {entityCode ? null : ( - `${option.id}`} - width={modalWidth - 50} - openOnFocus - renderInput={props => ( - - )} - filterOptions={(options, state) => - options.filter(option => + `${option.id}`} + width={modalWidth - 50} + openOnFocus + renderInput={props => ( + + )} + filterOptions={(options, state) => + options.filter( + option => + // if entityCodes are defined, filter out options that are not in the list + (entityCodes?.includes(option.code) ?? true) && RegexUtils.matchObjectProperties(state.inputValue, option, [ 'description', 'code', ]) - ) - } - onChange={(e, value) => - setDraft({ - ...draft, - entityCode: - (value as unknown as EntityRowFragment)?.code ?? '', - }) - } - /> - )} + ) + } + onChange={(e, value) => + setDraft({ + ...draft, + entityCode: (value as unknown as EntityRowFragment)?.code ?? '', + }) + } + /> {errorMessage ? ( []; isError: boolean; isLoading: boolean; - entityCode?: string; + entityCodes?: string[]; pagination?: TableProps['pagination']; updatePaginationQuery?: (page: number) => void; } @@ -36,7 +36,7 @@ const GS1ListComponent = ({ gs1Barcodes, isError, isLoading, - entityCode, + entityCodes, pagination, updatePaginationQuery, }: GS1ListProps) => { @@ -62,8 +62,8 @@ const GS1ListComponent = ({ return ( - {description.length > 35 - ? description.substring(0, 35) + '...' + {description.length > 50 + ? description.substring(0, 50) + '...' : description} @@ -90,7 +90,7 @@ const GS1ListComponent = ({ )} diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx index c14737488..543db49b8 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx @@ -5,6 +5,7 @@ import { GS1List } from './GS1List'; import { useEntityWithGS1s } from './api'; import { AppBarContentPortal, Typography } from '@common/components'; import { useTranslation } from '@common/intl'; +import { getGS1Barcodes, getPackSizeCodes } from './helpers'; export const GS1ListForEntityView = () => { const t = useTranslation('system'); @@ -17,20 +18,23 @@ export const GS1ListForEntityView = () => { setSuffix(t('label.details')); }, []); - const gs1Barcodes = entity?.gs1Barcodes.map(b => ({ ...b, entity })) ?? []; + if (!entity) return null; + + const gs1Barcodes = getGS1Barcodes(entity); + const entityCodes = getPackSizeCodes(entity); return ( <> - {entity?.description} + {entity.description} ); diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts index 47f864206..1b673d730 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts @@ -27,14 +27,14 @@ export type DeleteGs1MutationVariables = Types.Exact<{ export type DeleteGs1Mutation = { __typename?: 'FullMutation', deleteGs1: number }; -export type EntityWithGs1sFragment = { __typename?: 'EntityType', code: string, name: string, description: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }> }; +export type EntityWithGs1sFragment = { __typename?: 'EntityType', code: string, name: string, description: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> }; export type EntityWithGs1sQueryVariables = Types.Exact<{ code: Types.Scalars['String']['input']; }>; -export type EntityWithGs1sQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, description: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }> } | null }; +export type EntityWithGs1sQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, description: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, description: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, description: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> } | null }; export const Gs1FragmentDoc = gql` fragment GS1 on Gs1Node { @@ -53,15 +53,12 @@ export const EntityWithGs1sFragmentDoc = gql` code name description + type gs1Barcodes { id gtin manufacturer } - alternativeNames { - code - name - } } `; export const Gs1BarcodesDocument = gql` @@ -92,6 +89,12 @@ export const EntityWithGs1sDocument = gql` query entityWithGS1s($code: String!) { entity(code: $code) { ...EntityWithGS1s + children { + ...EntityWithGS1s + children { + ...EntityWithGS1s + } + } } } ${EntityWithGs1sFragmentDoc}`; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql index dd20cd6e2..7f8a58c98 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql +++ b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql @@ -34,6 +34,7 @@ fragment EntityWithGS1s on EntityType { code name description + type gs1Barcodes { id gtin @@ -44,5 +45,11 @@ fragment EntityWithGS1s on EntityType { query entityWithGS1s($code: String!) { entity(code: $code) { ...EntityWithGS1s + children { + ...EntityWithGS1s + children { + ...EntityWithGS1s + } + } } } diff --git a/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts b/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts index 87e03982d..7d0b98a95 100644 --- a/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts +++ b/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts @@ -1,4 +1,10 @@ -import { getParentDescription } from './helpers'; +import { EntityType } from '../../constants'; +import { + getParentDescription, + getPackSizeCodes, + EntityWithGs1Details, + getGS1Barcodes, +} from './helpers'; describe('getParentDescription', () => { it('returns full description when name doesnt match', () => { @@ -25,3 +31,166 @@ describe('getParentDescription', () => { ).toEqual('description name bla'); }); }); + +describe('getPackSizeCodes', () => { + it('returns an empty array when entity type is not PackSize', () => { + const entity: EntityWithGs1Details = { + type: EntityType.ActiveIngredients, + code: '123', + name: '123', + description: '123', + gs1Barcodes: [], + children: [ + { + type: EntityType.Brand, + code: '456', + name: '456', + description: '456', + gs1Barcodes: [], + children: [ + { + type: EntityType.Strength, + code: '789', + name: '789', + description: '789', + gs1Barcodes: [], + }, + ], + }, + ], + }; + + expect(getPackSizeCodes(entity)).toEqual([]); + }); + + it('returns an array with the entity code which entity type is PackSize', () => { + const entity: EntityWithGs1Details = { + type: EntityType.PackSize, + code: '123', + name: '123', + description: '123', + gs1Barcodes: [], + }; + + expect(getPackSizeCodes(entity)).toEqual(['123']); + }); + + it('returns an array with the child entity codes where entity type is PackSize', () => { + const entity: EntityWithGs1Details = { + type: EntityType.ImmediatePackaging, + code: '123', + name: '123', + description: '123', + gs1Barcodes: [], + children: [ + { + type: EntityType.PackSize, + code: '456', + name: '456', + description: '456', + gs1Barcodes: [], + }, + { + type: EntityType.PackSize, + code: '789', + name: '789', + description: '789', + gs1Barcodes: [], + }, + ], + }; + + expect(getPackSizeCodes(entity)).toEqual(['456', '789']); + }); +}); + +describe('getGS1Barcodes', () => { + it('returns an empty array when entity has no GS1 barcodes', () => { + const entity: EntityWithGs1Details = { + type: EntityType.Unit, + code: '123', + name: '123', + description: '123', + gs1Barcodes: [], + }; + + expect(getGS1Barcodes(entity)).toEqual([]); + }); + + it('returns an array with the entity GS1 barcodes', () => { + const entity: EntityWithGs1Details = { + type: EntityType.PackSize, + code: '123', + name: '123', + description: '123', + gs1Barcodes: [ + { id: '1234567890', gtin: '1234567890', manufacturer: 'X' }, + { id: '0987654321', gtin: '0987654321', manufacturer: 'Y' }, + ], + }; + + expect(getGS1Barcodes(entity)).toEqual([ + { id: '1234567890', gtin: '1234567890', manufacturer: 'X', entity }, + { id: '0987654321', gtin: '0987654321', manufacturer: 'Y', entity }, + ]); + }); + + it('returns an array with the entity and child GS1 barcodes', () => { + const entity = { + type: EntityType.ImmediatePackaging, + code: '123', + name: '123', + description: '123', + gs1Barcodes: [], + children: [ + { + type: EntityType.PackSize, + code: '456', + name: '456', + description: '456', + gs1Barcodes: [ + { id: '1234567890', gtin: '1234567890', manufacturer: 'X' }, + { id: '0987654321', gtin: '0987654321', manufacturer: 'Y' }, + ], + }, + { + type: EntityType.PackSize, + code: '789', + name: '789', + description: '789', + gs1Barcodes: [ + { id: '1111111111', gtin: '1111111111', manufacturer: 'X' }, + { id: '2222222222', gtin: '2222222222', manufacturer: 'Y' }, + ], + }, + ], + }; + + expect(getGS1Barcodes(entity)).toEqual([ + { + id: '1234567890', + gtin: '1234567890', + manufacturer: 'X', + entity: entity.children[0], + }, + { + id: '0987654321', + gtin: '0987654321', + manufacturer: 'Y', + entity: entity.children[0], + }, + { + id: '1111111111', + gtin: '1111111111', + manufacturer: 'X', + entity: entity.children[1], + }, + { + id: '2222222222', + gtin: '2222222222', + manufacturer: 'Y', + entity: entity.children[1], + }, + ]); + }); +}); diff --git a/frontend/system/src/Admin/GS1Barcodes/helpers.ts b/frontend/system/src/Admin/GS1Barcodes/helpers.ts index 1c6b8ca28..c783c6669 100644 --- a/frontend/system/src/Admin/GS1Barcodes/helpers.ts +++ b/frontend/system/src/Admin/GS1Barcodes/helpers.ts @@ -1,3 +1,13 @@ +import { EntityType } from '../../constants'; +import { + EntityWithGs1sFragment, + Gs1Fragment, +} from './api/operations.generated'; + +export type EntityWithGs1Details = EntityWithGs1sFragment & { + children?: EntityWithGs1Details[]; +}; + export const getParentDescription = ({ description, name, @@ -9,3 +19,46 @@ export const getParentDescription = ({ if (nameIndex === -1) return description; return description.substring(0, nameIndex).trim(); }; + +export const getPackSizeCodes = (entity?: EntityWithGs1Details | null) => { + const packSizeCodes: string[] = []; + + if (!entity) return packSizeCodes; + + if (entity.type === EntityType.PackSize) { + packSizeCodes.push(entity.code); + } + + const addChildCodes = (e: EntityWithGs1Details) => { + e.children?.forEach(c => { + if (c.type === EntityType.PackSize) { + packSizeCodes.push(c.code); + } + addChildCodes(c); + }); + }; + + addChildCodes(entity); + return packSizeCodes; +}; + +// GS1s for entity and all its children (though there should only be GS1s on PackSize entities, the lowest level...) +export const getGS1Barcodes = (entity: EntityWithGs1Details) => { + const barcodes: Omit[] = []; + + const addBarcodes = (e: EntityWithGs1Details) => + e.gs1Barcodes.forEach(b => barcodes.push({ ...b, entity: e })); + + addBarcodes(entity); + + const addChildBarcodes = (e: EntityWithGs1Details) => { + e.children?.forEach(c => { + addBarcodes(c); + addChildBarcodes(c); + }); + }; + + addChildBarcodes(entity); + + return barcodes; +}; From 4499ff66b5b74606dfd5d3a1b26b5c77c00b537c Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Wed, 31 Jan 2024 19:24:10 +1300 Subject: [PATCH 20/38] collapse nodes below unit --- frontend/system/src/Entities/EntityDetails.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/system/src/Entities/EntityDetails.tsx b/frontend/system/src/Entities/EntityDetails.tsx index 36f82ba8a..8924f7eb0 100644 --- a/frontend/system/src/Entities/EntityDetails.tsx +++ b/frontend/system/src/Entities/EntityDetails.tsx @@ -18,6 +18,13 @@ import { RouteBuilder } from '@common/utils'; import { AppRoute } from 'frontend/config/src'; import { useAuthContext } from '@common/authentication'; import { PermissionNode } from '@common/types'; +import { EntityType } from '../constants'; + +const TYPES_TO_COLLAPSE = [ + EntityType.Unit, + EntityType.ImmediatePackaging, + EntityType.PackSize, +]; export const EntityDetails = () => { const t = useTranslation('system'); @@ -39,7 +46,7 @@ export const EntityDetails = () => { const expandedIds: string[] = []; const addToExpandedIds = (ent?: EntityData | null) => { - if (ent) { + if (ent && !TYPES_TO_COLLAPSE.includes(ent.type as EntityType)) { expandedIds.push(ent.code); ent.children?.forEach(addToExpandedIds); } From 7c0a4a00450da27d54bbffc98398675f9f406e35 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 08:26:35 +1300 Subject: [PATCH 21/38] add body text to nothing here component --- frontend/common/src/intl/locales/en/system.json | 1 + frontend/system/src/Admin/GS1Barcodes/GS1List.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/common/src/intl/locales/en/system.json b/frontend/common/src/intl/locales/en/system.json index 23b129b5b..6e739b040 100644 --- a/frontend/common/src/intl/locales/en/system.json +++ b/frontend/common/src/intl/locales/en/system.json @@ -20,6 +20,7 @@ "error.unknown-error": "Unknown Error", "error.username-invalid-characters": "Username must not contain any special characters or spaces", "error.name-invalid-characters": "Name must not contain any special characters or spaces, and must start with a letter", + "error.no-gs1s": "No GS1 barcodes were found", "error.no-users": "No users", "error.no-pending-changes": "No changes are pending right now", "error.username-too-short": "Username must be at least 3 characters long", diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx b/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx index 1ca96935b..7e09d162f 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx +++ b/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx @@ -120,7 +120,7 @@ const GS1ListComponent = ({ data={gs1Barcodes} isLoading={isLoading} isError={isError} - noDataElement={} + noDataElement={} pagination={pagination} onChangePage={updatePaginationQuery} /> From 51ff442b7a186e014825cfd24f4d0e7c8cd9fcfd Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 09:12:10 +1300 Subject: [PATCH 22/38] show existing pack sizes in edit form --- frontend/system/src/Admin/EditEntity/helpers.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/system/src/Admin/EditEntity/helpers.ts b/frontend/system/src/Admin/EditEntity/helpers.ts index 937510c44..ea0d0bc1a 100644 --- a/frontend/system/src/Admin/EditEntity/helpers.ts +++ b/frontend/system/src/Admin/EditEntity/helpers.ts @@ -62,7 +62,14 @@ export const buildDrugInputFromEntity = (entity: EntityDetails): DrugInput => { ) .map(immPack => ({ ...getDetails(immPack), - packSizes: [], // to bring in later + packSizes: + immPack.children + ?.filter( + packSize => + packSize.type === EntityType.PackSize + ) + .map(packSize => getDetails(packSize)) || + [], })) || [], })) || [], })) || [], @@ -531,6 +538,7 @@ export const buildEntityDetailsFromPendingChangeBody = ( name: input.name || '', type: input.type || '', alternativeNames: input.alternativeNames || [], + gs1Barcodes: [], properties: input.properties?.map(p => ({ code: p.code, From fec01bd8d02c0229eb48d610d689329f295398a6 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 09:13:20 +1300 Subject: [PATCH 23/38] add link to entity GS1 page --- .../common/src/intl/locales/en/system.json | 1 + frontend/system/src/Entities/EntityData.tsx | 5 ++ .../system/src/Entities/EntityDetails.tsx | 3 +- .../system/src/Entities/EntityTreeItem.tsx | 18 ++++--- frontend/system/src/Entities/GS1Link.tsx | 53 +++++++++++++++++++ .../src/Entities/api/operations.generated.ts | 11 ++-- .../src/Entities/api/operations.graphql | 3 ++ 7 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 frontend/system/src/Entities/EntityData.tsx create mode 100644 frontend/system/src/Entities/GS1Link.tsx diff --git a/frontend/common/src/intl/locales/en/system.json b/frontend/common/src/intl/locales/en/system.json index 6e739b040..9725fbab9 100644 --- a/frontend/common/src/intl/locales/en/system.json +++ b/frontend/common/src/intl/locales/en/system.json @@ -68,6 +68,7 @@ "label.extra-descriptions": "Extra Descriptions", "label.form": "Form", "label.forms": "Forms", + "label.linked-gs1s": "{{ count }} Linked GS1s", "label.immediate-packaging": "Immediate Packaging", "label.invite-user": "Invite User", "label.lookup": "Look up", diff --git a/frontend/system/src/Entities/EntityData.tsx b/frontend/system/src/Entities/EntityData.tsx new file mode 100644 index 000000000..791f99bcf --- /dev/null +++ b/frontend/system/src/Entities/EntityData.tsx @@ -0,0 +1,5 @@ +import { EntityDetailsFragment } from './api/operations.generated'; + +export type EntityData = EntityDetailsFragment & { + children?: EntityData[] | null; +}; diff --git a/frontend/system/src/Entities/EntityDetails.tsx b/frontend/system/src/Entities/EntityDetails.tsx index 8924f7eb0..c486ec74e 100644 --- a/frontend/system/src/Entities/EntityDetails.tsx +++ b/frontend/system/src/Entities/EntityDetails.tsx @@ -13,7 +13,8 @@ import { useProduct } from './api'; import { FormControlLabel, Typography } from '@mui/material'; import { TreeView } from '@mui/lab'; import { useNavigate, useParams } from 'react-router-dom'; -import { EntityTreeItem, EntityData } from './EntityTreeItem'; +import { EntityTreeItem } from './EntityTreeItem'; +import { EntityData } from './EntityData'; import { RouteBuilder } from '@common/utils'; import { AppRoute } from 'frontend/config/src'; import { useAuthContext } from '@common/authentication'; diff --git a/frontend/system/src/Entities/EntityTreeItem.tsx b/frontend/system/src/Entities/EntityTreeItem.tsx index 8f5338623..5634e72a1 100644 --- a/frontend/system/src/Entities/EntityTreeItem.tsx +++ b/frontend/system/src/Entities/EntityTreeItem.tsx @@ -4,12 +4,10 @@ import { CopyIcon, FlatButton } from '@common/ui'; import { useNotification } from '@common/hooks'; import { Box, Link, Typography } from '@mui/material'; import { TreeItem } from '@mui/lab'; -import { EntityDetailsFragment } from './api/operations.generated'; import { usePropertyConfigurationItems } from '../Admin/Configuration/api/hooks/usePropertyConfigurationItems'; - -export type EntityData = EntityDetailsFragment & { - children?: EntityData[] | null; -}; +import { EntityType } from '../constants'; +import { EntityData } from './EntityData'; +import { GS1Link } from './GS1Link'; export const EntityTreeItem = ({ entity, @@ -40,7 +38,13 @@ export const EntityTreeItem = ({ }; const isLeaf = !entity.children?.length; - const showCode = showAllCodes || isLeaf || highlightCode === entity.code; + const showCode = + showAllCodes || + isLeaf || + highlightCode === entity.code || + // mSupply users will usually want these codes: + entity.type === EntityType.Strength || + entity.type === EntityType.Unit; // use default chevron icons, unless we're looking at a leaf node with no properties const customIcons = @@ -86,6 +90,8 @@ export const EntityTreeItem = ({ )} + + } > diff --git a/frontend/system/src/Entities/GS1Link.tsx b/frontend/system/src/Entities/GS1Link.tsx new file mode 100644 index 000000000..9e2173248 --- /dev/null +++ b/frontend/system/src/Entities/GS1Link.tsx @@ -0,0 +1,53 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from '@common/intl'; +import { EntityType } from '../constants'; +import { RouteBuilder } from '@common/utils'; +import { AppRoute } from 'frontend/config/src'; +import { EntityData } from './EntityData'; +import { Link } from 'frontend/common/src'; + +const SHOW_GS1_LINK_TYPES = [ + EntityType.Unit, + EntityType.ImmediatePackaging, + EntityType.PackSize, +]; + +export const GS1Link = ({ entity }: { entity: EntityData }) => { + const t = useTranslation('system'); + + if (!SHOW_GS1_LINK_TYPES.includes(entity.type as EntityType)) return <>; + + const barcodeCount = useMemo(() => getBarcodeCount(entity), [entity]); + + return ( + <> + {!!barcodeCount && ( + + {t('label.linked-gs1s', { count: barcodeCount })} + + )} + + ); +}; + +const getBarcodeCount = (entity: EntityData) => { + let count = 0; + + count += entity.gs1Barcodes.length; + + const countChildBarcodes = (e: EntityData) => { + e.children?.forEach(c => { + count += c.gs1Barcodes.length; + countChildBarcodes(c); + }); + }; + countChildBarcodes(entity); + + return count; +}; diff --git a/frontend/system/src/Entities/api/operations.generated.ts b/frontend/system/src/Entities/api/operations.generated.ts index 04b28ec0e..08bb93ed6 100644 --- a/frontend/system/src/Entities/api/operations.generated.ts +++ b/frontend/system/src/Entities/api/operations.generated.ts @@ -5,7 +5,7 @@ import * as Dom from 'graphql-request/dist/types.dom'; import gql from 'graphql-tag'; export type EntityRowFragment = { __typename?: 'EntityType', type: string, description: string, code: string, name: string, id: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', name: string }> }; -export type EntityDetailsFragment = { __typename?: 'EntityType', code: string, name: string, type: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }; +export type EntityDetailsFragment = { __typename?: 'EntityType', code: string, name: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }; export type EntitiesQueryVariables = Types.Exact<{ filter: Types.EntitySearchInput; @@ -21,14 +21,14 @@ export type EntityQueryVariables = Types.Exact<{ }>; -export type EntityQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; +export type EntityQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; export type ProductQueryVariables = Types.Exact<{ code: Types.Scalars['String']['input']; }>; -export type ProductQuery = { __typename?: 'FullQuery', product?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; +export type ProductQuery = { __typename?: 'FullQuery', product?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; export const EntityRowFragmentDoc = gql` fragment EntityRow on EntityType { @@ -47,6 +47,11 @@ export const EntityDetailsFragmentDoc = gql` code name type + gs1Barcodes { + id + gtin + manufacturer + } alternativeNames { code name diff --git a/frontend/system/src/Entities/api/operations.graphql b/frontend/system/src/Entities/api/operations.graphql index 5a1f5fc46..4332d480a 100644 --- a/frontend/system/src/Entities/api/operations.graphql +++ b/frontend/system/src/Entities/api/operations.graphql @@ -13,6 +13,9 @@ fragment EntityDetails on EntityType { code name type + gs1Barcodes { + id + } alternativeNames { code name From ade87446bc6c549bf5d4f845f32b6448d57aef06 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 12:12:58 +1300 Subject: [PATCH 24/38] gs1s -> barcodes --- .../src/{gs1/gs1.rs => barcode/barcode.rs} | 19 +-- .../src/{gs1/gs1s.rs => barcode/barcodes.rs} | 16 +-- .../delete_barcode.rs} | 38 +++--- .../insert_barcode.rs} | 40 +++--- backend/dgraph/src/{gs1 => barcode}/mod.rs | 14 +- backend/dgraph/src/entity.rs | 4 +- backend/dgraph/src/lib.rs | 8 +- backend/graphql/Cargo.toml | 2 +- backend/graphql/{gs1 => barcode}/Cargo.toml | 2 +- backend/graphql/barcode/src/lib.rs | 51 ++++++++ .../{gs1 => barcode}/src/mutations/delete.rs | 24 ++-- .../{gs1 => barcode}/src/mutations/insert.rs | 14 +- backend/graphql/barcode/src/mutations/mod.rs | 28 ++++ backend/graphql/barcode/src/types/barcode.rs | 60 +++++++++ .../{gs1 => barcode}/src/types/inputs.rs | 10 +- backend/graphql/barcode/src/types/mod.rs | 4 + backend/graphql/gs1/src/lib.rs | 45 ------- backend/graphql/gs1/src/mutations/mod.rs | 28 ---- backend/graphql/gs1/src/types/gs1.rs | 56 -------- backend/graphql/gs1/src/types/mod.rs | 4 - backend/graphql/lib.rs | 10 +- backend/graphql/types/src/types/log.rs | 12 +- .../src/types/{gs1.rs => barcode.rs} | 14 +- .../universal_codes/src/types/entity.rs | 10 +- .../universal_codes/src/types/mod.rs | 4 +- .../repository/src/db_diesel/audit_log_row.rs | 4 +- .../service/src/{gs1 => barcodes}/delete.rs | 24 ++-- backend/service/src/{gs1 => barcodes}/mod.rs | 73 ++++++----- .../src/{gs1 => barcodes}/tests/delete.rs | 30 ++--- .../src/{gs1 => barcodes}/tests/mod.rs | 0 .../src/{gs1 => barcodes}/tests/upsert.rs | 56 ++++---- backend/service/src/barcodes/upsert.rs | 117 +++++++++++++++++ backend/service/src/gs1/upsert.rs | 115 ---------------- backend/service/src/lib.rs | 2 +- backend/service/src/service_provider.rs | 6 +- data-loader/data/v2/schema.graphql | 4 +- .../common/src/intl/locales/en/common.json | 2 +- frontend/common/src/intl/locales/en/host.json | 2 +- .../common/src/intl/locales/en/system.json | 4 +- frontend/common/src/types/schema.ts | 106 +++++++-------- frontend/config/src/routes.ts | 2 +- .../src/components/AppDrawer/AppDrawer.tsx | 2 +- .../BarcodeEditModal.tsx} | 16 +-- .../GS1List.tsx => Barcodes/BarcodeList.tsx} | 38 +++--- .../BarcodeListForEntityView.tsx} | 20 +-- .../BarcodeListView.tsx} | 14 +- .../src/Admin/Barcodes/api/hooks/index.ts | 4 + .../api/hooks/useAddBarcode.ts} | 12 +- .../api/hooks/useBarcodes.ts} | 10 +- .../api/hooks/useDeleteBarcode.ts} | 8 +- .../api/hooks/useEntityWithBarcode.ts} | 8 +- .../{GS1Barcodes => Barcodes}/api/index.ts | 0 .../Barcodes/api/operations.generated.ts | 123 ++++++++++++++++++ .../src/Admin/Barcodes/api/operations.graphql | 55 ++++++++ .../{GS1Barcodes => Barcodes}/helpers.test.ts | 20 +-- .../{GS1Barcodes => Barcodes}/helpers.ts | 24 ++-- frontend/system/src/Admin/Barcodes/index.ts | 2 + .../system/src/Admin/EditEntity/helpers.ts | 2 +- .../src/Admin/GS1Barcodes/api/hooks/index.ts | 4 - .../GS1Barcodes/api/operations.generated.ts | 123 ------------------ .../Admin/GS1Barcodes/api/operations.graphql | 55 -------- .../system/src/Admin/GS1Barcodes/index.ts | 1 - frontend/system/src/Admin/Service.tsx | 9 +- .../Entities/{GS1Link.tsx => BarcodeLink.tsx} | 15 ++- .../system/src/Entities/EntityTreeItem.tsx | 4 +- .../src/Entities/api/operations.generated.ts | 10 +- .../src/Entities/api/operations.graphql | 2 +- frontend/system/src/queryKeys.ts | 4 +- 68 files changed, 838 insertions(+), 811 deletions(-) rename backend/dgraph/src/{gs1/gs1.rs => barcode/barcode.rs} (50%) rename backend/dgraph/src/{gs1/gs1s.rs => barcode/barcodes.rs} (51%) rename backend/dgraph/src/{gs1/delete_gs1.rs => barcode/delete_barcode.rs} (67%) rename backend/dgraph/src/{gs1/insert_gs1.rs => barcode/insert_barcode.rs} (64%) rename backend/dgraph/src/{gs1 => barcode}/mod.rs (56%) rename backend/graphql/{gs1 => barcode}/Cargo.toml (93%) create mode 100644 backend/graphql/barcode/src/lib.rs rename backend/graphql/{gs1 => barcode}/src/mutations/delete.rs (54%) rename backend/graphql/{gs1 => barcode}/src/mutations/insert.rs (60%) create mode 100644 backend/graphql/barcode/src/mutations/mod.rs create mode 100644 backend/graphql/barcode/src/types/barcode.rs rename backend/graphql/{gs1 => barcode}/src/types/inputs.rs (60%) create mode 100644 backend/graphql/barcode/src/types/mod.rs delete mode 100644 backend/graphql/gs1/src/lib.rs delete mode 100644 backend/graphql/gs1/src/mutations/mod.rs delete mode 100644 backend/graphql/gs1/src/types/gs1.rs delete mode 100644 backend/graphql/gs1/src/types/mod.rs rename backend/graphql_v1/universal_codes/src/types/{gs1.rs => barcode.rs} (67%) rename backend/service/src/{gs1 => barcodes}/delete.rs (52%) rename backend/service/src/{gs1 => barcodes}/mod.rs (50%) rename backend/service/src/{gs1 => barcodes}/tests/delete.rs (67%) rename backend/service/src/{gs1 => barcodes}/tests/mod.rs (100%) rename backend/service/src/{gs1 => barcodes}/tests/upsert.rs (70%) create mode 100644 backend/service/src/barcodes/upsert.rs delete mode 100644 backend/service/src/gs1/upsert.rs rename frontend/system/src/Admin/{GS1Barcodes/GS1EditModal.tsx => Barcodes/BarcodeEditModal.tsx} (93%) rename frontend/system/src/Admin/{GS1Barcodes/GS1List.tsx => Barcodes/BarcodeList.tsx} (74%) rename frontend/system/src/Admin/{GS1Barcodes/GS1ListForEntityView.tsx => Barcodes/BarcodeListForEntityView.tsx} (68%) rename frontend/system/src/Admin/{GS1Barcodes/GS1ListView.tsx => Barcodes/BarcodeListView.tsx} (63%) create mode 100644 frontend/system/src/Admin/Barcodes/api/hooks/index.ts rename frontend/system/src/Admin/{GS1Barcodes/api/hooks/useAddGS1.ts => Barcodes/api/hooks/useAddBarcode.ts} (60%) rename frontend/system/src/Admin/{GS1Barcodes/api/hooks/useGS1Barcodes.ts => Barcodes/api/hooks/useBarcodes.ts} (54%) rename frontend/system/src/Admin/{GS1Barcodes/api/hooks/useDeleteGS1.ts => Barcodes/api/hooks/useDeleteBarcode.ts} (53%) rename frontend/system/src/Admin/{GS1Barcodes/api/hooks/useEntityWithGS1s.ts => Barcodes/api/hooks/useEntityWithBarcode.ts} (50%) rename frontend/system/src/Admin/{GS1Barcodes => Barcodes}/api/index.ts (100%) create mode 100644 frontend/system/src/Admin/Barcodes/api/operations.generated.ts create mode 100644 frontend/system/src/Admin/Barcodes/api/operations.graphql rename frontend/system/src/Admin/{GS1Barcodes => Barcodes}/helpers.test.ts (92%) rename frontend/system/src/Admin/{GS1Barcodes => Barcodes}/helpers.ts (56%) create mode 100644 frontend/system/src/Admin/Barcodes/index.ts delete mode 100644 frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts delete mode 100644 frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts delete mode 100644 frontend/system/src/Admin/GS1Barcodes/api/operations.graphql delete mode 100644 frontend/system/src/Admin/GS1Barcodes/index.ts rename frontend/system/src/Entities/{GS1Link.tsx => BarcodeLink.tsx} (73%) diff --git a/backend/dgraph/src/gs1/gs1.rs b/backend/dgraph/src/barcode/barcode.rs similarity index 50% rename from backend/dgraph/src/gs1/gs1.rs rename to backend/dgraph/src/barcode/barcode.rs index b0ee2027c..ee3535a7b 100644 --- a/backend/dgraph/src/gs1/gs1.rs +++ b/backend/dgraph/src/barcode/barcode.rs @@ -1,17 +1,20 @@ use gql_client::GraphQLError; use serde::Serialize; -use crate::{DgraphClient, GS1Data, GS1}; +use crate::{Barcode, BarcodeData, DgraphClient}; #[derive(Serialize, Debug, Clone)] -struct GS1Vars { +struct BarcodeVars { gtin: String, } -pub async fn gs1_by_gtin(client: &DgraphClient, gtin: String) -> Result, GraphQLError> { +pub async fn barcode_by_gtin( + client: &DgraphClient, + gtin: String, +) -> Result, GraphQLError> { let query = r#" -query gs1($gtin: String!) { - data: queryGS1(filter: {gtin: {eq: $gtin}}) { +query barcode($gtin: String!) { + data: queryBarcode(filter: {gtin: {eq: $gtin}}) { gtin manufacturer entity { @@ -22,16 +25,16 @@ query gs1($gtin: String!) { } } "#; - let vars = GS1Vars { gtin }; + let vars = BarcodeVars { gtin }; let result = client .gql - .query_with_vars::(query, vars) + .query_with_vars::(query, vars) .await?; match result { Some(result) => match result.data.first() { - Some(gs1) => Ok(Some(gs1.clone())), + Some(barcode) => Ok(Some(barcode.clone())), None => Ok(None), }, None => Ok(None), diff --git a/backend/dgraph/src/gs1/gs1s.rs b/backend/dgraph/src/barcode/barcodes.rs similarity index 51% rename from backend/dgraph/src/gs1/gs1s.rs rename to backend/dgraph/src/barcode/barcodes.rs index fb27efc89..e8f50e13a 100644 --- a/backend/dgraph/src/gs1/gs1s.rs +++ b/backend/dgraph/src/barcode/barcodes.rs @@ -1,21 +1,21 @@ use gql_client::GraphQLError; use serde::Serialize; -use crate::{DgraphClient, GS1Data}; +use crate::{BarcodeData, DgraphClient}; #[derive(Serialize, Debug)] -pub struct GS1QueryVars { +pub struct BarcodeQueryVars { pub first: Option, pub offset: Option, } -pub async fn gs1s( +pub async fn barcodes( client: &DgraphClient, - vars: GS1QueryVars, -) -> Result, GraphQLError> { + vars: BarcodeQueryVars, +) -> Result, GraphQLError> { let query = r#" -query gs1s($first: Int, $offset: Int) { - data: queryGS1(first: $first, offset: $offset) { +query barcodes($first: Int, $offset: Int) { + data: queryBarcode(first: $first, offset: $offset) { manufacturer gtin entity { @@ -28,7 +28,7 @@ query gs1s($first: Int, $offset: Int) { "#; let data = client .gql - .query_with_vars::(query, vars) + .query_with_vars::(query, vars) .await?; Ok(data) diff --git a/backend/dgraph/src/gs1/delete_gs1.rs b/backend/dgraph/src/barcode/delete_barcode.rs similarity index 67% rename from backend/dgraph/src/gs1/delete_gs1.rs rename to backend/dgraph/src/barcode/delete_barcode.rs index 6822beaee..c23c67781 100644 --- a/backend/dgraph/src/gs1/delete_gs1.rs +++ b/backend/dgraph/src/barcode/delete_barcode.rs @@ -8,13 +8,13 @@ struct DeleteVars { gtin: String, } -pub async fn delete_gs1( +pub async fn delete_barcode( client: &DgraphClient, gtin: String, ) -> Result { let query = r#" -mutation DeleteGS1($gtin: String) { - data: deleteGS1(filter: {gtin: {eq: $gtin}}) { +mutation DeleteBarcode($gtin: String) { + data: deleteBarcode(filter: {gtin: {eq: $gtin}}) { numUids } }"#; @@ -37,20 +37,20 @@ mutation DeleteGS1($gtin: String) { #[cfg(test)] #[cfg(feature = "dgraph-tests")] mod tests { + use util::uuid::uuid; + use crate::{ - gs1::gs1::gs1_by_gtin, - insert_gs1::{insert_gs1, EntityCode, GS1Input}, + barcode::barcode::barcode_by_gtin, + insert_barcode::{insert_barcode, BarcodeInput, EntityCode}, }; - use util::uuid::uuid; use super::*; #[tokio::test] - async fn test_delete_configuration_item() { + async fn test_delete_barcode() { let client = DgraphClient::new("http://localhost:8080/graphql"); - // Create a GS1Input instance - let gs1_input = GS1Input { + let barcode_input = BarcodeInput { manufacturer: "test_manufacturer".to_string(), gtin: uuid(), entity: EntityCode { @@ -58,21 +58,21 @@ mod tests { }, }; - let result = insert_gs1(&client, gs1_input.clone(), true).await; + let result = insert_barcode(&client, barcode_input.clone(), true).await; if result.is_err() { println!( - "insert_gs1 err: {:#?} {:#?}", + "insert_barcode err: {:#?} {:#?}", result, result.clone().unwrap_err().json() ); }; - // GS1 exists - let result = gs1_by_gtin(&client, gs1_input.gtin.clone()).await; + // Barcode exists + let result = barcode_by_gtin(&client, barcode_input.gtin.clone()).await; if result.is_err() { println!( - "gs1_by_gtin err: {:#?} {:#?}", + "barcode_by_gtin err: {:#?} {:#?}", result, result.clone().unwrap_err().json() ); @@ -80,22 +80,22 @@ mod tests { assert!(result.unwrap().is_some()); - let result = delete_gs1(&client, gs1_input.gtin.clone()).await; + let result = delete_barcode(&client, barcode_input.gtin.clone()).await; if result.is_err() { println!( - "delete_gs1 err: {:#?} {:#?}", + "delete_barcode err: {:#?} {:#?}", result, result.clone().unwrap_err().json() ); }; assert!(result.is_ok()); - // GS1 no longer exists - let result = gs1_by_gtin(&client, gs1_input.gtin.clone()).await; + // Barcode no longer exists + let result = barcode_by_gtin(&client, barcode_input.gtin.clone()).await; if result.is_err() { println!( - "gs1_by_gtin err: {:#?} {:#?}", + "barcode_by_gtin err: {:#?} {:#?}", result, result.clone().unwrap_err().json() ); diff --git a/backend/dgraph/src/gs1/insert_gs1.rs b/backend/dgraph/src/barcode/insert_barcode.rs similarity index 64% rename from backend/dgraph/src/gs1/insert_gs1.rs rename to backend/dgraph/src/barcode/insert_barcode.rs index 894ffc4d6..8f1aa3a94 100644 --- a/backend/dgraph/src/gs1/insert_gs1.rs +++ b/backend/dgraph/src/barcode/insert_barcode.rs @@ -9,7 +9,7 @@ pub struct EntityCode { } #[derive(Serialize, Debug, Clone)] -pub struct GS1Input { +pub struct BarcodeInput { pub manufacturer: String, pub gtin: String, pub entity: EntityCode, @@ -17,22 +17,25 @@ pub struct GS1Input { #[derive(Serialize, Debug, Clone)] struct UpsertVars { - input: GS1Input, + input: BarcodeInput, upsert: bool, } -pub async fn insert_gs1( +pub async fn insert_barcode( client: &DgraphClient, - gs1: GS1Input, + barcode: BarcodeInput, upsert: bool, ) -> Result { let query = r#" -mutation AddGS1($input: [AddGS1Input!]!, $upsert: Boolean = false) { - data: addGS1(input: $input, upsert: $upsert) { +mutation AddBarcode($input: [AddBarcodeInput!]!, $upsert: Boolean = false) { + data: addBarcode(input: $input, upsert: $upsert) { numUids } }"#; - let variables = UpsertVars { input: gs1, upsert }; + let variables = UpsertVars { + input: barcode, + upsert, + }; let result = client .query_with_retry::(&query, variables) @@ -51,19 +54,18 @@ mutation AddGS1($input: [AddGS1Input!]!, $upsert: Boolean = false) { #[cfg(test)] #[cfg(feature = "dgraph-tests")] mod tests { - use crate::gs1::delete_gs1::delete_gs1; - use crate::gs1::gs1::gs1_by_gtin; + use crate::barcode::barcode::barcode_by_gtin; + use crate::barcode::delete_barcode::delete_barcode; use util::uuid::uuid; use super::*; #[tokio::test] - async fn test_insert_gs1() { + async fn test_insert_barcode() { // Create a DgraphClient instance let client = DgraphClient::new("http://localhost:8080/graphql"); - // Create a GS1Input instance - let gs1_input = GS1Input { + let barcode_input = BarcodeInput { manufacturer: "test_manufacturer".to_string(), gtin: uuid(), entity: EntityCode { @@ -71,30 +73,32 @@ mod tests { }, }; - let result = insert_gs1(&client, gs1_input.clone(), true).await; + let result = insert_barcode(&client, barcode_input.clone(), true).await; if result.is_err() { println!( - "insert_gs1 err: {:#?} {:#?}", + "insert_barcode err: {:#?} {:#?}", result, result.clone().unwrap_err().json() ); }; // Check if the new record can be found by querying for the gtin - let result = gs1_by_gtin(&client, gs1_input.gtin.clone()).await; + let result = barcode_by_gtin(&client, barcode_input.gtin.clone()).await; if result.is_err() { println!( - "gs1_by_gtin err: {:#?} {:#?}", + "barcode_by_gtin err: {:#?} {:#?}", result, result.clone().unwrap_err().json() ); }; let data = result.unwrap().unwrap(); - assert_eq!(data.manufacturer, gs1_input.manufacturer); + assert_eq!(data.manufacturer, barcode_input.manufacturer); // Delete the record - let _result = delete_gs1(&client, gs1_input.gtin.clone()).await.unwrap(); + let _result = delete_barcode(&client, barcode_input.gtin.clone()) + .await + .unwrap(); } } diff --git a/backend/dgraph/src/gs1/mod.rs b/backend/dgraph/src/barcode/mod.rs similarity index 56% rename from backend/dgraph/src/gs1/mod.rs rename to backend/dgraph/src/barcode/mod.rs index 1f70b0f3f..729f2661d 100644 --- a/backend/dgraph/src/gs1/mod.rs +++ b/backend/dgraph/src/barcode/mod.rs @@ -2,19 +2,19 @@ use serde::Deserialize; use crate::Entity; -pub mod delete_gs1; -pub mod gs1; -pub mod gs1s; -pub mod insert_gs1; +pub mod barcode; +pub mod barcodes; +pub mod delete_barcode; +pub mod insert_barcode; #[derive(Deserialize, Debug, Clone)] -pub struct GS1 { +pub struct Barcode { pub gtin: String, pub manufacturer: String, pub entity: Entity, } #[derive(Deserialize, Debug, Clone)] -pub struct GS1Data { - pub data: Vec, +pub struct BarcodeData { + pub data: Vec, } diff --git a/backend/dgraph/src/entity.rs b/backend/dgraph/src/entity.rs index 100f7b591..cec952c21 100644 --- a/backend/dgraph/src/entity.rs +++ b/backend/dgraph/src/entity.rs @@ -18,7 +18,7 @@ pub async fn entity_by_code( name description alternative_names - gs1s { + barcodes { gtin manufacturer } @@ -91,7 +91,7 @@ pub async fn entity_with_parents_by_code( code name description - gs1s { + barcodes { gtin manufacturer } diff --git a/backend/dgraph/src/lib.rs b/backend/dgraph/src/lib.rs index 6942f0fdf..b3d748c81 100644 --- a/backend/dgraph/src/lib.rs +++ b/backend/dgraph/src/lib.rs @@ -28,8 +28,8 @@ pub mod upsert_entity; pub use upsert_entity::*; pub mod link_codes; pub use link_codes::*; -pub mod gs1; -pub use gs1::*; +pub mod barcode; +pub use barcode::*; pub use gql_client::GraphQLError; @@ -76,7 +76,7 @@ pub struct Entity { #[serde(default)] pub alternative_names: Option, #[serde(default)] - pub gs1s: Vec, + pub barcodes: Vec, #[serde(default)] pub properties: Vec, #[serde(default)] @@ -96,7 +96,7 @@ pub struct Property { } #[derive(Deserialize, Debug, Clone)] -pub struct GS1Info { +pub struct BarcodeInfo { pub gtin: String, pub manufacturer: String, } diff --git a/backend/graphql/Cargo.toml b/backend/graphql/Cargo.toml index e7508fd4d..8da484d64 100644 --- a/backend/graphql/Cargo.toml +++ b/backend/graphql/Cargo.toml @@ -14,7 +14,7 @@ util = { path = "../util" } graphql_configuration = { path = "configuration" } graphql_drug_interactions = { path = "drug_interactions" } graphql_core = { path = "core" } -graphql_gs1 = { path = "gs1" } +graphql_barcode = { path = "barcode" } graphql_types = { path = "types" } graphql_general = { path = "general" } graphql_user_account = { path = "user_account" } diff --git a/backend/graphql/gs1/Cargo.toml b/backend/graphql/barcode/Cargo.toml similarity index 93% rename from backend/graphql/gs1/Cargo.toml rename to backend/graphql/barcode/Cargo.toml index 8703a17f5..bca968464 100644 --- a/backend/graphql/gs1/Cargo.toml +++ b/backend/graphql/barcode/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "graphql_gs1" +name = "graphql_barcode" version = "0.1.0" edition = "2018" diff --git a/backend/graphql/barcode/src/lib.rs b/backend/graphql/barcode/src/lib.rs new file mode 100644 index 000000000..5e6dbdeab --- /dev/null +++ b/backend/graphql/barcode/src/lib.rs @@ -0,0 +1,51 @@ +mod mutations; +use self::mutations::*; +mod types; + +use async_graphql::*; +use graphql_core::ContextExt; +use types::{ + AddBarcodeInput, BarcodeCollectionConnector, BarcodeCollectionResponse, BarcodeResponse, +}; + +#[derive(Default, Clone)] +pub struct BarcodeQueries; + +#[Object] +impl BarcodeQueries { + /// Get all barcodes + pub async fn barcodes( + &self, + ctx: &Context<'_>, + first: Option, + offset: Option, + ) -> Result { + let result = ctx + .service_provider() + .barcode_service + .barcodes(first, offset) + .await?; + + Ok(BarcodeCollectionResponse::Response( + BarcodeCollectionConnector::from_domain(result), + )) + } +} + +#[derive(Default, Clone)] +pub struct BarcodeMutations; + +#[Object] +impl BarcodeMutations { + async fn add_barcode( + &self, + ctx: &Context<'_>, + input: AddBarcodeInput, + ) -> Result { + add_barcode(ctx, input).await + } + + async fn delete_barcode(&self, ctx: &Context<'_>, gtin: String) -> Result { + delete_barcode(ctx, gtin).await + } +} diff --git a/backend/graphql/gs1/src/mutations/delete.rs b/backend/graphql/barcode/src/mutations/delete.rs similarity index 54% rename from backend/graphql/gs1/src/mutations/delete.rs rename to backend/graphql/barcode/src/mutations/delete.rs index 37c4b58c2..9fc406671 100644 --- a/backend/graphql/gs1/src/mutations/delete.rs +++ b/backend/graphql/barcode/src/mutations/delete.rs @@ -7,10 +7,10 @@ use graphql_core::{ use service::{ auth::{Resource, ResourceAccessRequest}, - gs1::ModifyGS1Error, + barcodes::ModifyBarcodeError, }; -pub async fn delete_gs1(ctx: &Context<'_>, gtin: String) -> Result { +pub async fn delete_barcode(ctx: &Context<'_>, gtin: String) -> Result { let user = validate_auth( ctx, &ResourceAccessRequest { @@ -21,8 +21,8 @@ pub async fn delete_gs1(ctx: &Context<'_>, gtin: String) -> Result { let service_context = ctx.service_context(Some(&user))?; match service_context .service_provider - .gs1_service - .delete_gs1( + .barcode_service + .delete_barcode( ctx.service_provider(), service_context.user_id.clone(), gtin, @@ -30,21 +30,21 @@ pub async fn delete_gs1(ctx: &Context<'_>, gtin: String) -> Result { .await { Ok(affected_items) => Ok(affected_items), - Err(error) => map_modify_gs1_error(error), + Err(error) => map_modify_barcode_error(error), } } -fn map_modify_gs1_error(error: ModifyGS1Error) -> Result { +fn map_modify_barcode_error(error: ModifyBarcodeError) -> Result { use StandardGraphqlError::*; let formatted_error = format!("{:#?}", error); let graphql_error = match error { - ModifyGS1Error::GS1AlreadyExists => BadUserInput(formatted_error), - ModifyGS1Error::GS1DoesNotExist => BadUserInput(formatted_error), - ModifyGS1Error::UniversalCodeDoesNotExist => BadUserInput(formatted_error), - ModifyGS1Error::InternalError(message) => InternalError(message), - ModifyGS1Error::DatabaseError(_) => InternalError(formatted_error), - ModifyGS1Error::DgraphError(gql_error) => { + ModifyBarcodeError::BarcodeAlreadyExists => BadUserInput(formatted_error), + ModifyBarcodeError::BarcodeDoesNotExist => BadUserInput(formatted_error), + ModifyBarcodeError::UniversalCodeDoesNotExist => BadUserInput(formatted_error), + ModifyBarcodeError::InternalError(message) => InternalError(message), + ModifyBarcodeError::DatabaseError(_) => InternalError(formatted_error), + ModifyBarcodeError::DgraphError(gql_error) => { InternalError(format!("{:#?} - {:?}", gql_error, gql_error.json())) } }; diff --git a/backend/graphql/gs1/src/mutations/insert.rs b/backend/graphql/barcode/src/mutations/insert.rs similarity index 60% rename from backend/graphql/gs1/src/mutations/insert.rs rename to backend/graphql/barcode/src/mutations/insert.rs index 015e46259..3703edf31 100644 --- a/backend/graphql/gs1/src/mutations/insert.rs +++ b/backend/graphql/barcode/src/mutations/insert.rs @@ -5,11 +5,11 @@ use graphql_core::{standard_graphql_error::validate_auth, ContextExt}; use service::auth::{Resource, ResourceAccessRequest}; use crate::{ - map_modify_gs1_error, - types::{AddGS1Input, GS1Node, GS1Response}, + map_modify_barcode_error, + types::{AddBarcodeInput, BarcodeNode, BarcodeResponse}, }; -pub async fn add_gs1(ctx: &Context<'_>, input: AddGS1Input) -> Result { +pub async fn add_barcode(ctx: &Context<'_>, input: AddBarcodeInput) -> Result { let user = validate_auth( ctx, &ResourceAccessRequest { @@ -20,15 +20,15 @@ pub async fn add_gs1(ctx: &Context<'_>, input: AddGS1Input) -> Result Ok(GS1Response::Response(GS1Node::from_domain(gs1))), - Err(error) => map_modify_gs1_error(error), + Ok(barcode) => Ok(BarcodeResponse::Response(BarcodeNode::from_domain(barcode))), + Err(error) => map_modify_barcode_error(error), } } diff --git a/backend/graphql/barcode/src/mutations/mod.rs b/backend/graphql/barcode/src/mutations/mod.rs new file mode 100644 index 000000000..15ae286bc --- /dev/null +++ b/backend/graphql/barcode/src/mutations/mod.rs @@ -0,0 +1,28 @@ +use async_graphql::*; +use graphql_core::standard_graphql_error::StandardGraphqlError; +use service::barcodes::ModifyBarcodeError; + +mod delete; +pub use delete::*; +mod insert; +pub use insert::*; + +use crate::types::BarcodeResponse; + +pub fn map_modify_barcode_error(error: ModifyBarcodeError) -> Result { + use StandardGraphqlError::*; + let formatted_error = format!("{:#?}", error); + + let graphql_error = match error { + ModifyBarcodeError::BarcodeAlreadyExists => BadUserInput(formatted_error), + ModifyBarcodeError::BarcodeDoesNotExist => BadUserInput(formatted_error), + ModifyBarcodeError::UniversalCodeDoesNotExist => BadUserInput(formatted_error), + ModifyBarcodeError::InternalError(message) => InternalError(message), + ModifyBarcodeError::DatabaseError(_) => InternalError(formatted_error), + ModifyBarcodeError::DgraphError(gql_error) => { + InternalError(format!("{:#?} - {:?}", gql_error, gql_error.json())) + } + }; + + Err(graphql_error.extend()) +} diff --git a/backend/graphql/barcode/src/types/barcode.rs b/backend/graphql/barcode/src/types/barcode.rs new file mode 100644 index 000000000..b55a517dd --- /dev/null +++ b/backend/graphql/barcode/src/types/barcode.rs @@ -0,0 +1,60 @@ +use async_graphql::*; +use dgraph::Barcode; +use graphql_universal_codes_v1::EntityType; +use service::barcodes::BarcodeCollection; + +#[derive(Clone, Debug)] +pub struct BarcodeNode { + pub row: Barcode, +} + +impl BarcodeNode { + pub fn from_domain(barcode: Barcode) -> BarcodeNode { + BarcodeNode { row: barcode } + } +} + +#[Object] +impl BarcodeNode { + pub async fn id(&self) -> &str { + &self.row.gtin + } + + pub async fn gtin(&self) -> &str { + &self.row.gtin + } + pub async fn manufacturer(&self) -> &str { + &self.row.manufacturer + } + pub async fn entity(&self) -> EntityType { + EntityType::from_domain(self.row.entity.clone()) + } +} + +#[derive(Debug, SimpleObject)] +pub struct BarcodeCollectionConnector { + pub data: Vec, + pub total_count: u32, +} + +impl BarcodeCollectionConnector { + pub fn from_domain(results: BarcodeCollection) -> BarcodeCollectionConnector { + BarcodeCollectionConnector { + total_count: results.total_length, + data: results + .data + .into_iter() + .map(BarcodeNode::from_domain) + .collect(), + } + } +} + +#[derive(Union)] +pub enum BarcodeResponse { + Response(BarcodeNode), +} +#[derive(Union)] +pub enum BarcodeCollectionResponse { + Response(BarcodeCollectionConnector), +} diff --git a/backend/graphql/gs1/src/types/inputs.rs b/backend/graphql/barcode/src/types/inputs.rs similarity index 60% rename from backend/graphql/gs1/src/types/inputs.rs rename to backend/graphql/barcode/src/types/inputs.rs index 043977313..00afda3f0 100644 --- a/backend/graphql/gs1/src/types/inputs.rs +++ b/backend/graphql/barcode/src/types/inputs.rs @@ -1,16 +1,16 @@ use async_graphql::*; -use service::gs1::upsert::AddGS1; +use service::barcodes::upsert::AddBarcode; #[derive(InputObject, Clone)] -pub struct AddGS1Input { +pub struct AddBarcodeInput { pub gtin: String, pub manufacturer: String, pub entity_code: String, } -impl From for AddGS1 { - fn from(input: AddGS1Input) -> Self { - AddGS1 { +impl From for AddBarcode { + fn from(input: AddBarcodeInput) -> Self { + AddBarcode { gtin: input.gtin, manufacturer: input.manufacturer, entity_code: input.entity_code, diff --git a/backend/graphql/barcode/src/types/mod.rs b/backend/graphql/barcode/src/types/mod.rs new file mode 100644 index 000000000..c3469dce7 --- /dev/null +++ b/backend/graphql/barcode/src/types/mod.rs @@ -0,0 +1,4 @@ +mod barcode; +pub use barcode::*; +mod inputs; +pub use inputs::*; diff --git a/backend/graphql/gs1/src/lib.rs b/backend/graphql/gs1/src/lib.rs deleted file mode 100644 index 1b984fcd7..000000000 --- a/backend/graphql/gs1/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -mod mutations; -use self::mutations::*; -mod types; - -use async_graphql::*; -use graphql_core::ContextExt; -use types::{AddGS1Input, GS1CollectionConnector, GS1CollectionResponse, GS1Response}; - -#[derive(Default, Clone)] -pub struct GS1Queries; - -#[Object] -impl GS1Queries { - /// Get all GS1s - pub async fn gs1_barcodes( - &self, - ctx: &Context<'_>, - first: Option, - offset: Option, - ) -> Result { - let result = ctx - .service_provider() - .gs1_service - .gs1s(first, offset) - .await?; - - Ok(GS1CollectionResponse::Response( - GS1CollectionConnector::from_domain(result), - )) - } -} - -#[derive(Default, Clone)] -pub struct GS1Mutations; - -#[Object] -impl GS1Mutations { - async fn add_gs1(&self, ctx: &Context<'_>, input: AddGS1Input) -> Result { - add_gs1(ctx, input).await - } - - async fn delete_gs1(&self, ctx: &Context<'_>, gtin: String) -> Result { - delete_gs1(ctx, gtin).await - } -} diff --git a/backend/graphql/gs1/src/mutations/mod.rs b/backend/graphql/gs1/src/mutations/mod.rs deleted file mode 100644 index 634f21a67..000000000 --- a/backend/graphql/gs1/src/mutations/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -use async_graphql::*; -use graphql_core::standard_graphql_error::StandardGraphqlError; -use service::gs1::ModifyGS1Error; - -mod delete; -pub use delete::*; -mod insert; -pub use insert::*; - -use crate::types::GS1Response; - -pub fn map_modify_gs1_error(error: ModifyGS1Error) -> Result { - use StandardGraphqlError::*; - let formatted_error = format!("{:#?}", error); - - let graphql_error = match error { - ModifyGS1Error::GS1AlreadyExists => BadUserInput(formatted_error), - ModifyGS1Error::GS1DoesNotExist => BadUserInput(formatted_error), - ModifyGS1Error::UniversalCodeDoesNotExist => BadUserInput(formatted_error), - ModifyGS1Error::InternalError(message) => InternalError(message), - ModifyGS1Error::DatabaseError(_) => InternalError(formatted_error), - ModifyGS1Error::DgraphError(gql_error) => { - InternalError(format!("{:#?} - {:?}", gql_error, gql_error.json())) - } - }; - - Err(graphql_error.extend()) -} diff --git a/backend/graphql/gs1/src/types/gs1.rs b/backend/graphql/gs1/src/types/gs1.rs deleted file mode 100644 index 33b63083b..000000000 --- a/backend/graphql/gs1/src/types/gs1.rs +++ /dev/null @@ -1,56 +0,0 @@ -use async_graphql::*; -use dgraph::GS1; -use graphql_universal_codes_v1::EntityType; -use service::gs1::GS1Collection; - -#[derive(Clone, Debug)] -pub struct GS1Node { - pub row: GS1, -} - -impl GS1Node { - pub fn from_domain(gs1: GS1) -> GS1Node { - GS1Node { row: gs1 } - } -} - -#[Object] -impl GS1Node { - pub async fn id(&self) -> &str { - &self.row.gtin - } - - pub async fn gtin(&self) -> &str { - &self.row.gtin - } - pub async fn manufacturer(&self) -> &str { - &self.row.manufacturer - } - pub async fn entity(&self) -> EntityType { - EntityType::from_domain(self.row.entity.clone()) - } -} - -#[derive(Debug, SimpleObject)] -pub struct GS1CollectionConnector { - pub data: Vec, - pub total_count: u32, -} - -impl GS1CollectionConnector { - pub fn from_domain(results: GS1Collection) -> GS1CollectionConnector { - GS1CollectionConnector { - total_count: results.total_length, - data: results.data.into_iter().map(GS1Node::from_domain).collect(), - } - } -} - -#[derive(Union)] -pub enum GS1Response { - Response(GS1Node), -} -#[derive(Union)] -pub enum GS1CollectionResponse { - Response(GS1CollectionConnector), -} diff --git a/backend/graphql/gs1/src/types/mod.rs b/backend/graphql/gs1/src/types/mod.rs deleted file mode 100644 index 48c70de12..000000000 --- a/backend/graphql/gs1/src/types/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod gs1; -pub use gs1::*; -mod inputs; -pub use inputs::*; diff --git a/backend/graphql/lib.rs b/backend/graphql/lib.rs index 03a44d2e5..f568c1ba7 100644 --- a/backend/graphql/lib.rs +++ b/backend/graphql/lib.rs @@ -7,12 +7,12 @@ use actix_web::HttpResponse; use actix_web::{guard, HttpRequest}; use async_graphql::{EmptySubscription, MergedObject, SchemaBuilder}; use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; +use graphql_barcode::{BarcodeMutations, BarcodeQueries}; use graphql_configuration::{ConfigurationMutations, ConfigurationQueries}; use graphql_core::loader::LoaderRegistry; use graphql_core::{refresh_token_from_cookie, RefreshTokenData, SelfRequest}; use graphql_drug_interactions::{DrugInteractionMutations, DrugInteractionQueries}; use graphql_general::GeneralQueries; -use graphql_gs1::{GS1Mutations, GS1Queries}; use graphql_universal_codes::{UniversalCodesMutations, UniversalCodesQueries}; use graphql_universal_codes_v1::UniversalCodesV1Queries; use graphql_user_account::{UserAccountMutations, UserAccountQueries}; @@ -31,7 +31,7 @@ pub struct FullQuery( pub UniversalCodesQueries, pub UniversalCodesV1Queries, pub ConfigurationQueries, - pub GS1Queries, + pub BarcodeQueries, pub DrugInteractionQueries, ); @@ -40,7 +40,7 @@ pub struct FullMutation( pub UserAccountMutations, pub UniversalCodesMutations, pub ConfigurationMutations, - pub GS1Mutations, + pub BarcodeMutations, pub DrugInteractionMutations, ); @@ -54,7 +54,7 @@ pub fn full_query() -> FullQuery { UniversalCodesQueries, UniversalCodesV1Queries, ConfigurationQueries, - GS1Queries, + BarcodeQueries, DrugInteractionQueries, ) } @@ -64,7 +64,7 @@ pub fn full_mutation() -> FullMutation { UserAccountMutations, UniversalCodesMutations, ConfigurationMutations, - GS1Mutations, + BarcodeMutations, DrugInteractionMutations, ) } diff --git a/backend/graphql/types/src/types/log.rs b/backend/graphql/types/src/types/log.rs index 492a3ec3e..bbc689b96 100644 --- a/backend/graphql/types/src/types/log.rs +++ b/backend/graphql/types/src/types/log.rs @@ -28,8 +28,8 @@ pub enum LogNodeType { UniversalCodeChangeApproved, UniversalCodeChangeRejected, UniversalCodeChangeRequested, - GS1Created, - GS1Deleted, + BarcodeCreated, + BarcodeDeleted, ConfigurationItemCreated, ConfigurationItemDeleted, PropertyConfigurationItemUpserted, @@ -99,8 +99,8 @@ impl LogNodeType { LogType::PropertyConfigurationItemUpserted => { LogNodeType::PropertyConfigurationItemUpserted } - LogType::GS1Created => LogNodeType::GS1Created, - LogType::GS1Deleted => LogNodeType::GS1Deleted, + LogType::BarcodeCreated => LogNodeType::BarcodeCreated, + LogType::BarcodeDeleted => LogNodeType::BarcodeDeleted, LogType::InteractionGroupUpserted => LogNodeType::InteractionGroupUpserted, LogType::InteractionGroupDeleted => LogNodeType::InteractionGroupDeleted, } @@ -124,8 +124,8 @@ impl LogNodeType { LogNodeType::PropertyConfigurationItemUpserted => { LogType::PropertyConfigurationItemUpserted } - LogNodeType::GS1Created => LogType::GS1Created, - LogNodeType::GS1Deleted => LogType::GS1Deleted, + LogNodeType::BarcodeCreated => LogType::BarcodeCreated, + LogNodeType::BarcodeDeleted => LogType::BarcodeDeleted, LogNodeType::InteractionGroupUpserted => LogType::InteractionGroupUpserted, LogNodeType::InteractionGroupDeleted => LogType::InteractionGroupDeleted, } diff --git a/backend/graphql_v1/universal_codes/src/types/gs1.rs b/backend/graphql_v1/universal_codes/src/types/barcode.rs similarity index 67% rename from backend/graphql_v1/universal_codes/src/types/gs1.rs rename to backend/graphql_v1/universal_codes/src/types/barcode.rs index 9f72c5fb0..bade37b0f 100644 --- a/backend/graphql_v1/universal_codes/src/types/gs1.rs +++ b/backend/graphql_v1/universal_codes/src/types/barcode.rs @@ -1,14 +1,14 @@ use async_graphql::*; -use dgraph::GS1Info; +use dgraph::BarcodeInfo; #[derive(Clone, Debug)] -pub struct GS1Type { +pub struct BarcodeType { pub manufacturer: String, pub gtin: String, } #[Object] -impl GS1Type { +impl BarcodeType { pub async fn id(&self) -> &str { &self.gtin } @@ -22,11 +22,11 @@ impl GS1Type { } } -impl GS1Type { - pub fn from_domain(entity_gs1s: Vec) -> Vec { - entity_gs1s +impl BarcodeType { + pub fn from_domain(entity_barcodes: Vec) -> Vec { + entity_barcodes .into_iter() - .map(|g| GS1Type { + .map(|g| BarcodeType { gtin: g.gtin, manufacturer: g.manufacturer, }) diff --git a/backend/graphql_v1/universal_codes/src/types/entity.rs b/backend/graphql_v1/universal_codes/src/types/entity.rs index 54c30f9fb..cef1d7e6f 100644 --- a/backend/graphql_v1/universal_codes/src/types/entity.rs +++ b/backend/graphql_v1/universal_codes/src/types/entity.rs @@ -2,7 +2,7 @@ use async_graphql::*; use dgraph::Entity; use crate::AlternativeNameType; -use crate::GS1Type; +use crate::BarcodeType; use super::DrugInteractionType; use super::PropertiesType; @@ -16,7 +16,7 @@ pub struct EntityType { pub r#type: String, pub category: String, pub alternative_names: Vec, - pub gs1s: Vec, + pub barcodes: Vec, pub properties: Vec, pub children: Vec, pub parents: Vec, @@ -31,7 +31,7 @@ impl EntityType { description: entity.description, r#type: entity.r#type, category: entity.category, - gs1s: GS1Type::from_domain(entity.gs1s), + barcodes: BarcodeType::from_domain(entity.barcodes), properties: PropertiesType::from_domain(entity.properties), alternative_names: match entity.alternative_names { Some(names) => AlternativeNameType::from_domain(names), @@ -71,8 +71,8 @@ impl EntityType { get_type_for_entity(&self) } - pub async fn gs1_barcodes(&self) -> &Vec { - &self.gs1s + pub async fn barcodes(&self) -> &Vec { + &self.barcodes } pub async fn properties(&self) -> &Vec { diff --git a/backend/graphql_v1/universal_codes/src/types/mod.rs b/backend/graphql_v1/universal_codes/src/types/mod.rs index ba3d4b99c..ee9f6dc03 100644 --- a/backend/graphql_v1/universal_codes/src/types/mod.rs +++ b/backend/graphql_v1/universal_codes/src/types/mod.rs @@ -10,5 +10,5 @@ mod entity_search_input; pub use entity_search_input::*; mod entity_sort; pub use entity_sort::*; -mod gs1; -pub use gs1::*; +mod barcode; +pub use barcode::*; diff --git a/backend/repository/src/db_diesel/audit_log_row.rs b/backend/repository/src/db_diesel/audit_log_row.rs index 481220ee7..ec3381b2b 100644 --- a/backend/repository/src/db_diesel/audit_log_row.rs +++ b/backend/repository/src/db_diesel/audit_log_row.rs @@ -28,8 +28,8 @@ pub enum LogType { UniversalCodeChangeRequested, UniversalCodeCreated, UniversalCodeUpdated, - GS1Created, - GS1Deleted, + BarcodeCreated, + BarcodeDeleted, ConfigurationItemCreated, ConfigurationItemDeleted, PropertyConfigurationItemUpserted, diff --git a/backend/service/src/gs1/delete.rs b/backend/service/src/barcodes/delete.rs similarity index 52% rename from backend/service/src/gs1/delete.rs rename to backend/service/src/barcodes/delete.rs index 07c122357..719c95862 100644 --- a/backend/service/src/gs1/delete.rs +++ b/backend/service/src/barcodes/delete.rs @@ -5,26 +5,28 @@ use crate::{ service_provider::{ServiceContext, ServiceProvider}, }; use chrono::Utc; -use dgraph::{delete_gs1::delete_gs1 as dgraph_delete_gs1, gs1::gs1::gs1_by_gtin}; +use dgraph::{ + barcode::barcode::barcode_by_gtin, delete_barcode::delete_barcode as dgraph_delete_barcode, +}; use repository::LogType; -use super::ModifyGS1Error; +use super::ModifyBarcodeError; -pub async fn delete_gs1( +pub async fn delete_barcode( sp: Arc, user_id: String, client: dgraph::DgraphClient, gtin: String, -) -> Result { +) -> Result { validate(&client, gtin.clone()).await?; - let result = dgraph_delete_gs1(&client, gtin.clone()).await?; + let result = dgraph_delete_barcode(&client, gtin.clone()).await?; // Audit logging let service_context = ServiceContext::with_user(sp.clone(), user_id)?; audit_log_entry( &service_context, - LogType::GS1Deleted, + LogType::BarcodeDeleted, Some(gtin), Utc::now().naive_utc(), )?; @@ -32,16 +34,16 @@ pub async fn delete_gs1( Ok(result.numUids) } -async fn validate(client: &dgraph::DgraphClient, gtin: String) -> Result<(), ModifyGS1Error> { - // Check that the gtin does exist - let result = gs1_by_gtin(client, gtin.clone()).await.map_err(|e| { - ModifyGS1Error::InternalError(format!("Failed to get gs1 by gtin: {}", e.message())) +async fn validate(client: &dgraph::DgraphClient, gtin: String) -> Result<(), ModifyBarcodeError> { + // Check that the barcode does exist + let result = barcode_by_gtin(client, gtin.clone()).await.map_err(|e| { + ModifyBarcodeError::InternalError(format!("Failed to get barcode by gtin: {}", e.message())) })?; match result { Some(_) => {} None => { - return Err(ModifyGS1Error::GS1DoesNotExist); + return Err(ModifyBarcodeError::BarcodeDoesNotExist); } } diff --git a/backend/service/src/gs1/mod.rs b/backend/service/src/barcodes/mod.rs similarity index 50% rename from backend/service/src/gs1/mod.rs rename to backend/service/src/barcodes/mod.rs index e7998a377..1259fcdd1 100644 --- a/backend/service/src/gs1/mod.rs +++ b/backend/service/src/barcodes/mod.rs @@ -4,9 +4,9 @@ use std::{ }; use dgraph::{ - gs1::gs1::gs1_by_gtin, - gs1s::{gs1s, GS1QueryVars}, - DgraphClient, GraphQLError, GS1, + barcode::barcode::barcode_by_gtin, + barcodes::{barcodes, BarcodeQueryVars}, + Barcode, DgraphClient, GraphQLError, }; use repository::RepositoryError; use util::usize_to_u32; @@ -14,24 +14,24 @@ use util::usize_to_u32; use crate::{service_provider::ServiceProvider, settings::Settings}; #[derive(Debug)] -pub enum ModifyGS1Error { - GS1DoesNotExist, - GS1AlreadyExists, +pub enum ModifyBarcodeError { + BarcodeDoesNotExist, + BarcodeAlreadyExists, UniversalCodeDoesNotExist, InternalError(String), DatabaseError(RepositoryError), DgraphError(GraphQLError), } -impl From for ModifyGS1Error { +impl From for ModifyBarcodeError { fn from(error: RepositoryError) -> Self { - ModifyGS1Error::DatabaseError(error) + ModifyBarcodeError::DatabaseError(error) } } -impl From for ModifyGS1Error { +impl From for ModifyBarcodeError { fn from(error: GraphQLError) -> Self { - ModifyGS1Error::DgraphError(error) + ModifyBarcodeError::DgraphError(error) } } @@ -40,35 +40,35 @@ mod tests; pub mod delete; pub mod upsert; -pub struct GS1Service { +pub struct BarcodeService { client: DgraphClient, } #[derive(Debug)] -pub enum GS1ServiceError { +pub enum BarcodeServiceError { InternalError(String), BadUserInput(String), } -impl Display for GS1ServiceError { +impl Display for BarcodeServiceError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - GS1ServiceError::InternalError(details) => { + BarcodeServiceError::InternalError(details) => { write!(f, "Internal error: {}", details) } - GS1ServiceError::BadUserInput(details) => { + BarcodeServiceError::BadUserInput(details) => { write!(f, "Bad user input: {}", details) } } } } -pub struct GS1Collection { - pub data: Vec, +pub struct BarcodeCollection { + pub data: Vec, pub total_length: u32, } -impl GS1Service { +impl BarcodeService { pub fn new(settings: Settings) -> Self { let url = format!( "{}:{}/graphql", @@ -76,36 +76,39 @@ impl GS1Service { settings.dgraph.port ); - GS1Service { + BarcodeService { client: DgraphClient::new(&url), } } - pub async fn gs1s( + pub async fn barcodes( &self, first: Option, offset: Option, - ) -> Result { - let result = gs1s(&self.client, GS1QueryVars { first, offset }) + ) -> Result { + let result = barcodes(&self.client, BarcodeQueryVars { first, offset }) .await - .map_err(|e| GS1ServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? + .map_err(|e| BarcodeServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? match result { - Some(data) => Ok(GS1Collection { + Some(data) => Ok(BarcodeCollection { total_length: usize_to_u32(data.data.len()), data: data.data, }), - None => Ok(GS1Collection { + None => Ok(BarcodeCollection { data: vec![], total_length: 0, }), } } - pub async fn gs1_by_gtin(&self, gtin: String) -> Result, GS1ServiceError> { - let result = gs1_by_gtin(&self.client, gtin) + pub async fn barcode_by_gtin( + &self, + gtin: String, + ) -> Result, BarcodeServiceError> { + let result = barcode_by_gtin(&self.client, gtin) .await - .map_err(|e| GS1ServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? + .map_err(|e| BarcodeServiceError::InternalError(e.message().to_string()))?; // TODO: Improve error handling? match result { Some(result) => Ok(Some(result)), @@ -113,21 +116,21 @@ impl GS1Service { } } - pub async fn add_gs1( + pub async fn add_barcode( &self, sp: Arc, user_id: String, - item: upsert::AddGS1, - ) -> Result { - upsert::add_gs1(sp, user_id, self.client.clone(), item).await + item: upsert::AddBarcode, + ) -> Result { + upsert::add_barcode(sp, user_id, self.client.clone(), item).await } - pub async fn delete_gs1( + pub async fn delete_barcode( &self, sp: Arc, user_id: String, gtin: String, - ) -> Result { - delete::delete_gs1(sp, user_id, self.client.clone(), gtin).await + ) -> Result { + delete::delete_barcode(sp, user_id, self.client.clone(), gtin).await } } diff --git a/backend/service/src/gs1/tests/delete.rs b/backend/service/src/barcodes/tests/delete.rs similarity index 67% rename from backend/service/src/gs1/tests/delete.rs rename to backend/service/src/barcodes/tests/delete.rs index 3421835e6..deb27330b 100644 --- a/backend/service/src/gs1/tests/delete.rs +++ b/backend/service/src/barcodes/tests/delete.rs @@ -5,40 +5,40 @@ mod test { use std::sync::Arc; use util::uuid::uuid; - use crate::gs1::upsert::AddGS1; + use crate::barcodes::upsert::AddBarcode; use crate::service_provider::ServiceContext; use crate::service_provider::ServiceProvider; use crate::test_utils::get_test_settings; #[actix_rt::test] - async fn delete_gs1_success() { + async fn delete_barcode_success() { let (_, _, connection_manager, _) = - setup_all("delete_gs1_success", MockDataInserts::none()).await; + setup_all("delete_barcode_success", MockDataInserts::none()).await; let service_provider = Arc::new(ServiceProvider::new( connection_manager, get_test_settings(""), )); let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); - let service = &context.service_provider.gs1_service; + let service = &context.service_provider.barcode_service; let new_gtin = uuid(); - let input = AddGS1 { + let input = AddBarcode { gtin: new_gtin.clone(), manufacturer: "test_manufacturer".to_string(), entity_code: "c7750265".to_string(), }; let result = service - .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .add_barcode(service_provider.clone(), context.user_id.clone(), input) .await .unwrap(); assert_eq!(result.gtin, new_gtin); - // Delete the newly created gs1 + // Delete the newly created barcode let _result = service - .delete_gs1( + .delete_barcode( service_provider.clone(), context.user_id.clone(), new_gtin.clone(), @@ -46,28 +46,28 @@ mod test { .await .unwrap(); - // Check the gs1 no longer exists - let result = service.gs1_by_gtin(new_gtin.clone()).await.unwrap(); + // Check the barcode no longer exists + let result = service.barcode_by_gtin(new_gtin.clone()).await.unwrap(); assert!(result.is_none()); } #[actix_rt::test] - async fn delete_gs1_gtin_doesnt_exist() { + async fn delete_barcode_gtin_doesnt_exist() { let (_, _, connection_manager, _) = - setup_all("delete_gs1_gtin_doesnt_exist", MockDataInserts::none()).await; + setup_all("delete_barcode_gtin_doesnt_exist", MockDataInserts::none()).await; let service_provider = Arc::new(ServiceProvider::new( connection_manager, get_test_settings(""), )); let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); - let service = &context.service_provider.gs1_service; + let service = &context.service_provider.barcode_service; let some_gtin = uuid(); - // Try delete non-existent gs1 + // Try delete non-existent barcode let result = service - .delete_gs1( + .delete_barcode( service_provider.clone(), context.user_id.clone(), some_gtin.clone(), diff --git a/backend/service/src/gs1/tests/mod.rs b/backend/service/src/barcodes/tests/mod.rs similarity index 100% rename from backend/service/src/gs1/tests/mod.rs rename to backend/service/src/barcodes/tests/mod.rs diff --git a/backend/service/src/gs1/tests/upsert.rs b/backend/service/src/barcodes/tests/upsert.rs similarity index 70% rename from backend/service/src/gs1/tests/upsert.rs rename to backend/service/src/barcodes/tests/upsert.rs index 0408411c2..0e41736be 100644 --- a/backend/service/src/gs1/tests/upsert.rs +++ b/backend/service/src/barcodes/tests/upsert.rs @@ -5,40 +5,40 @@ mod test { use std::sync::Arc; use util::uuid::uuid; - use crate::gs1::upsert::AddGS1; + use crate::barcodes::upsert::AddBarcode; use crate::service_provider::ServiceContext; use crate::service_provider::ServiceProvider; use crate::test_utils::get_test_settings; #[actix_rt::test] - async fn add_gs1_success() { + async fn add_barcode_success() { let (_, _, connection_manager, _) = - setup_all("add_gs1_success", MockDataInserts::none()).await; + setup_all("add_barcode_success", MockDataInserts::none()).await; let service_provider = Arc::new(ServiceProvider::new( connection_manager, get_test_settings(""), )); let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); - let service = &context.service_provider.gs1_service; + let service = &context.service_provider.barcode_service; let new_gtin = uuid(); - let input = AddGS1 { + let input = AddBarcode { gtin: new_gtin.clone(), manufacturer: "test_manufacturer".to_string(), entity_code: "c7750265".to_string(), }; let result = service - .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .add_barcode(service_provider.clone(), context.user_id.clone(), input) .await .unwrap(); assert_eq!(result.gtin, new_gtin); - // Delete the newly created gs1 + // Delete the newly created barcode let _result = service - .delete_gs1( + .delete_barcode( service_provider.clone(), context.user_id.clone(), new_gtin.clone(), @@ -48,51 +48,51 @@ mod test { } #[actix_rt::test] - async fn add_gs1_no_gtin() { + async fn add_barcode_no_gtin() { let (_, _, connection_manager, _) = - setup_all("add_gs1_no_gtin", MockDataInserts::none()).await; + setup_all("add_barcode_no_gtin", MockDataInserts::none()).await; let service_provider = Arc::new(ServiceProvider::new( connection_manager, get_test_settings(""), )); let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); - let service = &context.service_provider.gs1_service; + let service = &context.service_provider.barcode_service; - let input = AddGS1 { + let input = AddBarcode { gtin: "".to_string(), manufacturer: "test_manufacturer".to_string(), entity_code: "c7750265".to_string(), }; let result = service - .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .add_barcode(service_provider.clone(), context.user_id.clone(), input) .await; assert!(result.is_err()); } #[actix_rt::test] - async fn add_gs1_gs1_already_exists() { + async fn add_barcode_already_exists() { let (_, _, connection_manager, _) = - setup_all("add_gs1_gs1_already_exists", MockDataInserts::none()).await; + setup_all("add_barcode_already_exists", MockDataInserts::none()).await; let service_provider = Arc::new(ServiceProvider::new( connection_manager, get_test_settings(""), )); let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); - let service = &context.service_provider.gs1_service; + let service = &context.service_provider.barcode_service; - // Add new GS1 + // Add new barcode let new_gtin = uuid(); - let input = AddGS1 { + let input = AddBarcode { gtin: new_gtin.clone(), manufacturer: "test_manufacturer".to_string(), entity_code: "c7750265".to_string(), }; let result = service - .add_gs1( + .add_barcode( service_provider.clone(), context.user_id.clone(), input.clone(), @@ -102,20 +102,20 @@ mod test { assert_eq!(result.gtin, new_gtin); // Try add another with same GTIN - let input = AddGS1 { + let input = AddBarcode { gtin: new_gtin.clone(), manufacturer: "another_manufacturer".to_string(), entity_code: "6d8482f7".to_string(), }; let result = service - .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .add_barcode(service_provider.clone(), context.user_id.clone(), input) .await; assert!(result.is_err()); - // Delete the newly created gs1 + // Delete the newly created barcode let _result = service - .delete_gs1( + .delete_barcode( service_provider.clone(), context.user_id.clone(), new_gtin.clone(), @@ -125,26 +125,26 @@ mod test { } #[actix_rt::test] - async fn add_gs1_entity_code_not_found() { + async fn add_barcode_entity_code_not_found() { let (_, _, connection_manager, _) = - setup_all("add_gs1_entity_code_not_found", MockDataInserts::none()).await; + setup_all("add_barcode_entity_code_not_found", MockDataInserts::none()).await; let service_provider = Arc::new(ServiceProvider::new( connection_manager, get_test_settings(""), )); let context = ServiceContext::as_server_admin(service_provider.clone()).unwrap(); - let service = &context.service_provider.gs1_service; + let service = &context.service_provider.barcode_service; let new_gtin = uuid(); - let input = AddGS1 { + let input = AddBarcode { gtin: new_gtin.clone(), manufacturer: "test_manufacturer".to_string(), entity_code: "doesn't exist".to_string(), }; let result = service - .add_gs1(service_provider.clone(), context.user_id.clone(), input) + .add_barcode(service_provider.clone(), context.user_id.clone(), input) .await; assert!(result.is_err()); } diff --git a/backend/service/src/barcodes/upsert.rs b/backend/service/src/barcodes/upsert.rs new file mode 100644 index 000000000..15ade851b --- /dev/null +++ b/backend/service/src/barcodes/upsert.rs @@ -0,0 +1,117 @@ +use std::sync::Arc; + +use crate::{ + audit_log::audit_log_entry, + service_provider::{ServiceContext, ServiceProvider}, +}; +use chrono::Utc; +use dgraph::{ + barcode::barcode::barcode_by_gtin, + entity, + insert_barcode::{insert_barcode, BarcodeInput, EntityCode}, + Barcode, +}; +use repository::LogType; + +use super::ModifyBarcodeError; + +#[derive(Clone, Debug)] +pub struct AddBarcode { + pub gtin: String, + pub manufacturer: String, + pub entity_code: String, +} + +pub async fn add_barcode( + sp: Arc, + user_id: String, + client: dgraph::DgraphClient, + new_barcode: AddBarcode, +) -> Result { + // Validate + validate(&client, &new_barcode).await?; + + // Generate + let barcode_input = generate(new_barcode); + + let _result = insert_barcode(&client, barcode_input.clone(), true).await?; + + // Audit logging + let service_context = ServiceContext::with_user(sp.clone(), user_id)?; + audit_log_entry( + &service_context, + LogType::BarcodeCreated, + Some(barcode_input.gtin.clone()), + Utc::now().naive_utc(), + )?; + + // Query to get the newly created barcode + let result = barcode_by_gtin(&client, barcode_input.gtin) + .await + .map_err(|e| { + ModifyBarcodeError::InternalError(format!( + "Failed to get newly created barcode by gtin: {}", + e.message() + )) + })?; + + let result = match result { + Some(result) => result, + None => { + return Err(ModifyBarcodeError::InternalError( + "Unable to find newly created barcode".to_string(), + )) + } + }; + + Ok(result) +} + +pub fn generate(new_barcode: AddBarcode) -> BarcodeInput { + BarcodeInput { + gtin: new_barcode.gtin.clone(), + manufacturer: new_barcode.manufacturer.clone(), + entity: EntityCode { + code: new_barcode.entity_code.clone(), + }, + } +} + +pub async fn validate( + _client: &dgraph::DgraphClient, + new_barcode: &AddBarcode, +) -> Result<(), ModifyBarcodeError> { + if new_barcode.gtin.is_empty() { + return Err(ModifyBarcodeError::InternalError( + "GTIN is required".to_string(), + )); + } + + if new_barcode.manufacturer.is_empty() { + return Err(ModifyBarcodeError::InternalError( + "Manufacturer is required".to_string(), + )); + } + + if new_barcode.entity_code.is_empty() { + return Err(ModifyBarcodeError::InternalError( + "Entity code is required".to_string(), + )); + } + + let existing = barcode_by_gtin(_client, new_barcode.gtin.clone()).await?; + + match existing { + Some(_) => return Err(ModifyBarcodeError::BarcodeAlreadyExists), + None => {} + } + + let entity = entity::entity_by_code(_client, new_barcode.entity_code.clone()).await?; + + match entity { + Some(_) => {} + None => return Err(ModifyBarcodeError::UniversalCodeDoesNotExist), + } + + Ok(()) +} diff --git a/backend/service/src/gs1/upsert.rs b/backend/service/src/gs1/upsert.rs deleted file mode 100644 index 49a46bb9e..000000000 --- a/backend/service/src/gs1/upsert.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::sync::Arc; - -use crate::{ - audit_log::audit_log_entry, - service_provider::{ServiceContext, ServiceProvider}, -}; -use chrono::Utc; -use dgraph::{ - entity, - gs1::gs1::gs1_by_gtin, - insert_gs1::{insert_gs1, EntityCode, GS1Input}, - GS1, -}; -use repository::LogType; - -use super::ModifyGS1Error; - -#[derive(Clone, Debug)] -pub struct AddGS1 { - pub gtin: String, - pub manufacturer: String, - pub entity_code: String, -} - -pub async fn add_gs1( - sp: Arc, - user_id: String, - client: dgraph::DgraphClient, - new_gs1: AddGS1, -) -> Result { - // Validate - validate(&client, &new_gs1).await?; - - // Generate - let gs1_input = generate(new_gs1); - - let _result = insert_gs1(&client, gs1_input.clone(), true).await?; - - // Audit logging - let service_context = ServiceContext::with_user(sp.clone(), user_id)?; - audit_log_entry( - &service_context, - LogType::GS1Created, - Some(gs1_input.gtin.clone()), - Utc::now().naive_utc(), - )?; - - // Query to get the newly created gs1 - let result = gs1_by_gtin(&client, gs1_input.gtin).await.map_err(|e| { - ModifyGS1Error::InternalError(format!( - "Failed to get newly created gs1 by gtin: {}", - e.message() - )) - })?; - - let result = match result { - Some(result) => result, - None => { - return Err(ModifyGS1Error::InternalError( - "Unable to find newly created gs1".to_string(), - )) - } - }; - - Ok(result) -} - -pub fn generate(new_gs1: AddGS1) -> GS1Input { - GS1Input { - gtin: new_gs1.gtin.clone(), - manufacturer: new_gs1.manufacturer.clone(), - entity: EntityCode { - code: new_gs1.entity_code.clone(), - }, - } -} - -pub async fn validate( - _client: &dgraph::DgraphClient, - new_gs1: &AddGS1, -) -> Result<(), ModifyGS1Error> { - if new_gs1.gtin.is_empty() { - return Err(ModifyGS1Error::InternalError( - "GTIN is required".to_string(), - )); - } - - if new_gs1.manufacturer.is_empty() { - return Err(ModifyGS1Error::InternalError( - "Manufacturer is required".to_string(), - )); - } - - if new_gs1.entity_code.is_empty() { - return Err(ModifyGS1Error::InternalError( - "Entity code is required".to_string(), - )); - } - - let existing = gs1_by_gtin(_client, new_gs1.gtin.clone()).await?; - - match existing { - Some(_) => return Err(ModifyGS1Error::GS1AlreadyExists), - None => {} - } - - let entity = entity::entity_by_code(_client, new_gs1.entity_code.clone()).await?; - - match entity { - Some(_) => {} - None => return Err(ModifyGS1Error::UniversalCodeDoesNotExist), - } - - Ok(()) -} diff --git a/backend/service/src/lib.rs b/backend/service/src/lib.rs index fb4eadfe7..c06e8a9bc 100644 --- a/backend/service/src/lib.rs +++ b/backend/service/src/lib.rs @@ -4,10 +4,10 @@ use repository::{Pagination, PaginationOption, DEFAULT_PAGINATION_LIMIT}; pub mod audit_log; pub mod auth; pub mod auth_data; +pub mod barcodes; pub mod configuration; pub mod drug_interactions; pub mod email; -pub mod gs1; pub mod log_service; pub mod login; pub mod service_provider; diff --git a/backend/service/src/service_provider.rs b/backend/service/src/service_provider.rs index 480a608f1..597065806 100644 --- a/backend/service/src/service_provider.rs +++ b/backend/service/src/service_provider.rs @@ -4,10 +4,10 @@ use repository::{RepositoryError, StorageConnection, StorageConnectionManager}; use crate::{ auth::{AuthService, AuthServiceTrait}, + barcodes::BarcodeService, configuration::ConfigurationService, drug_interactions::DrugInteractionService, email::{EmailService, EmailServiceTrait}, - gs1::GS1Service, log_service::{LogService, LogServiceTrait}, settings::Settings, universal_codes::UniversalCodesService, @@ -19,7 +19,7 @@ pub struct ServiceProvider { pub email_service: Box, pub universal_codes_service: Box, pub configuration_service: Box, - pub gs1_service: Box, + pub barcode_service: Box, pub drug_interaction_service: Box, pub validation_service: Box, pub user_account_service: Box, @@ -74,7 +74,7 @@ impl ServiceProvider { email_service: Box::new(EmailService::new(settings.clone())), universal_codes_service: Box::new(UniversalCodesService::new(settings.clone())), configuration_service: Box::new(ConfigurationService::new(settings.clone())), - gs1_service: Box::new(GS1Service::new(settings.clone())), + barcode_service: Box::new(BarcodeService::new(settings.clone())), drug_interaction_service: Box::new(DrugInteractionService::new(settings.clone())), validation_service: Box::new(AuthService::new()), user_account_service: Box::new(UserAccountService {}), diff --git a/data-loader/data/v2/schema.graphql b/data-loader/data/v2/schema.graphql index f204a42ad..afa342467 100644 --- a/data-loader/data/v2/schema.graphql +++ b/data-loader/data/v2/schema.graphql @@ -17,7 +17,7 @@ type Entity { properties: [Property] @dgraph(pred: "properties") children: [Entity] @dgraph(pred: "children") parents: [Entity] @dgraph(pred: "~children") - gs1s: [GS1] @dgraph(pred: "gs1s") @hasInverse(field: entity) + barcodes: [Barcode] @dgraph(pred: "barcodes") @hasInverse(field: entity) interaction_groups: [DrugInteractionGroup] @dgraph(pred: "interaction_groups") @hasInverse(field: drugs) @@ -75,7 +75,7 @@ type PropertyConfigurationItem { url: String @dgraph(pred: "url") } -type GS1 { +type Barcode { id: ID! gtin: String! @id @dgraph(pred: "code") @search(by: [exact, trigram]) manufacturer: String! @dgraph(pred: "manufacturer") diff --git a/frontend/common/src/intl/locales/en/common.json b/frontend/common/src/intl/locales/en/common.json index 9bd7807ba..e508b7090 100644 --- a/frontend/common/src/intl/locales/en/common.json +++ b/frontend/common/src/intl/locales/en/common.json @@ -89,7 +89,7 @@ "label.expand-all": "Expand all", "label.expiry": "Expiry", "label.event": "Event", - "label.gtin": "GTIN", + "label.gtin": "Barcode/GTIN", "label.hours": "Hours", "label.hours_one": "Hour", "label.hours_other": "Hours", diff --git a/frontend/common/src/intl/locales/en/host.json b/frontend/common/src/intl/locales/en/host.json index fa7b805de..22fdd1746 100644 --- a/frontend/common/src/intl/locales/en/host.json +++ b/frontend/common/src/intl/locales/en/host.json @@ -10,7 +10,7 @@ "auth.timeout-message": "You have been logged out of your session due to inactivity. Click OK to return to the login screen.", "auth.timeout-title": "Session Timed Out", "auth.unauthenticated-message": "You are not currently logged in. Click OK to return to the login screen.", - "barcodes": "GS1 Barcodes", + "barcodes": "Barcodes", "browse": "Browse", "button.activate-account": "Activate Account", "button.close-the-menu": "Close the menu", diff --git a/frontend/common/src/intl/locales/en/system.json b/frontend/common/src/intl/locales/en/system.json index 9725fbab9..e6210d4a0 100644 --- a/frontend/common/src/intl/locales/en/system.json +++ b/frontend/common/src/intl/locales/en/system.json @@ -20,7 +20,7 @@ "error.unknown-error": "Unknown Error", "error.username-invalid-characters": "Username must not contain any special characters or spaces", "error.name-invalid-characters": "Name must not contain any special characters or spaces, and must start with a letter", - "error.no-gs1s": "No GS1 barcodes were found", + "error.no-barcodes": "No barcodes were found", "error.no-users": "No users", "error.no-pending-changes": "No changes are pending right now", "error.username-too-short": "Username must be at least 3 characters long", @@ -68,7 +68,6 @@ "label.extra-descriptions": "Extra Descriptions", "label.form": "Form", "label.forms": "Forms", - "label.linked-gs1s": "{{ count }} Linked GS1s", "label.immediate-packaging": "Immediate Packaging", "label.invite-user": "Invite User", "label.lookup": "Look up", @@ -83,6 +82,7 @@ "label.presentation": "Presentation", "label.properties": "Properties", "label.reject": "Reject", + "label.related-barcodes": "{{ count }} Related Barcodes", "label.route": "Route", "label.routes": "Routes", "label.unit": "Unit", diff --git a/frontend/common/src/types/schema.ts b/frontend/common/src/types/schema.ts index 1758c5eb2..1b3294b7f 100644 --- a/frontend/common/src/types/schema.ts +++ b/frontend/common/src/types/schema.ts @@ -27,17 +27,17 @@ export type AccessDenied = LogoutErrorInterface & { fullError: Scalars['String']['output']; }; -export type AddConfigurationItemInput = { - name: Scalars['String']['input']; - type: ConfigurationItemTypeInput; -}; - -export type AddGs1Input = { +export type AddBarcodeInput = { entityCode: Scalars['String']['input']; gtin: Scalars['String']['input']; manufacturer: Scalars['String']['input']; }; +export type AddConfigurationItemInput = { + name: Scalars['String']['input']; + type: ConfigurationItemTypeInput; +}; + export type AlternativeNameInput = { code: Scalars['String']['input']; name: Scalars['String']['input']; @@ -67,6 +67,31 @@ export type AuthTokenErrorInterface = { export type AuthTokenResponse = AuthToken | AuthTokenError; +export type BarcodeCollectionConnector = { + __typename: 'BarcodeCollectionConnector'; + data: Array; + totalCount: Scalars['Int']['output']; +}; + +export type BarcodeCollectionResponse = BarcodeCollectionConnector; + +export type BarcodeNode = { + __typename: 'BarcodeNode'; + entity: EntityType; + gtin: Scalars['String']['output']; + id: Scalars['String']['output']; + manufacturer: Scalars['String']['output']; +}; + +export type BarcodeResponse = BarcodeNode; + +export type BarcodeType = { + __typename: 'BarcodeType'; + gtin: Scalars['String']['output']; + id: Scalars['String']['output']; + manufacturer: Scalars['String']['output']; +}; + export enum ChangeStatusNode { Approved = 'APPROVED', Pending = 'PENDING', @@ -173,10 +198,10 @@ export type EntitySortInput = { export type EntityType = { __typename: 'EntityType'; alternativeNames: Array; + barcodes: Array; children: Array; code: Scalars['String']['output']; description: Scalars['String']['output']; - gs1Barcodes: Array; interactions?: Maybe>; name: Scalars['String']['output']; parents: Array; @@ -202,13 +227,13 @@ export type FullMutation = { __typename: 'FullMutation'; /** Updates user account based on a token and their information (Response to initiate_user_invite) */ acceptUserInvite: InviteUserResponse; + addBarcode: BarcodeResponse; addConfigurationItem: Scalars['Int']['output']; - addGs1: Gs1Response; approvePendingChange: UpsertEntityResponse; createUserAccount: CreateUserAccountResponse; + deleteBarcode: Scalars['Int']['output']; deleteConfigurationItem: Scalars['Int']['output']; deleteDrugInteractionGroup: Scalars['Int']['output']; - deleteGs1: Scalars['Int']['output']; deleteUserAccount: DeleteUserAccountResponse; /** * Initiates the password reset flow for a user based on email address @@ -236,13 +261,13 @@ export type FullMutationAcceptUserInviteArgs = { }; -export type FullMutationAddConfigurationItemArgs = { - input: AddConfigurationItemInput; +export type FullMutationAddBarcodeArgs = { + input: AddBarcodeInput; }; -export type FullMutationAddGs1Args = { - input: AddGs1Input; +export type FullMutationAddConfigurationItemArgs = { + input: AddConfigurationItemInput; }; @@ -257,18 +282,18 @@ export type FullMutationCreateUserAccountArgs = { }; -export type FullMutationDeleteConfigurationItemArgs = { - code: Scalars['String']['input']; +export type FullMutationDeleteBarcodeArgs = { + gtin: Scalars['String']['input']; }; -export type FullMutationDeleteDrugInteractionGroupArgs = { +export type FullMutationDeleteConfigurationItemArgs = { code: Scalars['String']['input']; }; -export type FullMutationDeleteGs1Args = { - gtin: Scalars['String']['input']; +export type FullMutationDeleteDrugInteractionGroupArgs = { + code: Scalars['String']['input']; }; @@ -338,13 +363,13 @@ export type FullQuery = { * The refresh token is returned as a cookie */ authToken: AuthTokenResponse; + /** Get all barcodes */ + barcodes: BarcodeCollectionResponse; /** Get the configuration items for a given type. */ configurationItems: ConfigurationItemsResponse; entities: EntityCollectionType; /** Query "universal codes" entry by code */ entity?: Maybe; - /** Get all GS1s */ - gs1Barcodes: Gs1CollectionResponse; logout: LogoutResponse; logs: LogResponse; me: UserResponse; @@ -369,6 +394,12 @@ export type FullQueryAuthTokenArgs = { }; +export type FullQueryBarcodesArgs = { + first?: InputMaybe; + offset?: InputMaybe; +}; + + export type FullQueryConfigurationItemsArgs = { type: ConfigurationItemTypeInput; }; @@ -386,12 +417,6 @@ export type FullQueryEntityArgs = { }; -export type FullQueryGs1BarcodesArgs = { - first?: InputMaybe; - offset?: InputMaybe; -}; - - export type FullQueryLogsArgs = { filter?: InputMaybe; page?: InputMaybe; @@ -421,31 +446,6 @@ export type FullQueryUserAccountsArgs = { sort?: InputMaybe>; }; -export type Gs1CollectionConnector = { - __typename: 'Gs1CollectionConnector'; - data: Array; - totalCount: Scalars['Int']['output']; -}; - -export type Gs1CollectionResponse = Gs1CollectionConnector; - -export type Gs1Node = { - __typename: 'Gs1Node'; - entity: EntityType; - gtin: Scalars['String']['output']; - id: Scalars['String']['output']; - manufacturer: Scalars['String']['output']; -}; - -export type Gs1Response = Gs1Node; - -export type Gs1Type = { - __typename: 'Gs1Type'; - gtin: Scalars['String']['output']; - id: Scalars['String']['output']; - manufacturer: Scalars['String']['output']; -}; - export type IdResponse = { __typename: 'IdResponse'; id: Scalars['String']['output']; @@ -504,10 +504,10 @@ export type LogNode = { }; export enum LogNodeType { + BarcodeCreated = 'BARCODE_CREATED', + BarcodeDeleted = 'BARCODE_DELETED', ConfigurationItemCreated = 'CONFIGURATION_ITEM_CREATED', ConfigurationItemDeleted = 'CONFIGURATION_ITEM_DELETED', - Gs1Created = 'GS1_CREATED', - Gs1Deleted = 'GS1_DELETED', InteractionGroupDeleted = 'INTERACTION_GROUP_DELETED', InteractionGroupUpserted = 'INTERACTION_GROUP_UPSERTED', PropertyConfigurationItemUpserted = 'PROPERTY_CONFIGURATION_ITEM_UPSERTED', diff --git a/frontend/config/src/routes.ts b/frontend/config/src/routes.ts index 99b7cecd8..83ab547fd 100644 --- a/frontend/config/src/routes.ts +++ b/frontend/config/src/routes.ts @@ -14,7 +14,7 @@ export enum AppRoute { Interactions = 'interactions', Edit = 'edit', PendingChanges = 'pending-changes', - GS1Barcodes = 'barcodes', + Barcodes = 'barcodes', Settings = 'settings', Logout = 'logout', diff --git a/frontend/host/src/components/AppDrawer/AppDrawer.tsx b/frontend/host/src/components/AppDrawer/AppDrawer.tsx index bc4b7c576..85c70b094 100644 --- a/frontend/host/src/components/AppDrawer/AppDrawer.tsx +++ b/frontend/host/src/components/AppDrawer/AppDrawer.tsx @@ -207,7 +207,7 @@ export const AppDrawer: React.FC = () => { /> } text={t('barcodes')} diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx b/frontend/system/src/Admin/Barcodes/BarcodeEditModal.tsx similarity index 93% rename from frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx rename to frontend/system/src/Admin/Barcodes/BarcodeEditModal.tsx index ab2a4b570..d862cbb0e 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1EditModal.tsx +++ b/frontend/system/src/Admin/Barcodes/BarcodeEditModal.tsx @@ -9,31 +9,31 @@ import { import { useDialog } from '@common/hooks'; import { CheckIcon } from '@common/icons'; import { useTranslation } from '@common/intl'; -import { AddGs1Input } from '@common/types'; +import { AddBarcodeInput } from '@common/types'; import { Grid, Typography } from '@common/ui'; import { RegexUtils } from '@common/utils'; import { Alert, AlertTitle } from '@mui/material'; import React, { useState } from 'react'; import { useEntities } from '../../Entities/api'; import { EntityRowFragment } from '../../Entities/api/operations.generated'; -import { useAddGS1 } from './api'; +import { useAddBarcode } from './api'; import { getParentDescription } from './helpers'; -type GS1EditModalProps = { +type BarcodeEditModalProps = { isOpen: boolean; onClose: () => void; entityCodes?: string[]; }; -export const GS1EditModal = ({ +export const BarcodeEditModal = ({ isOpen, onClose, entityCodes, -}: GS1EditModalProps) => { +}: BarcodeEditModalProps) => { const t = useTranslation('system'); const [errorMessage, setErrorMessage] = useState(null); - const [draft, setDraft] = useState({ + const [draft, setDraft] = useState({ entityCode: '', gtin: '', manufacturer: '', @@ -47,11 +47,11 @@ export const GS1EditModal = ({ offset: 0, }); - const { mutateAsync: addGs1, isLoading } = useAddGS1(); + const { mutateAsync: addBarcode, isLoading } = useAddBarcode(); const onSubmit = async () => { try { - await addGs1({ + await addBarcode({ input: { ...draft, }, diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx b/frontend/system/src/Admin/Barcodes/BarcodeList.tsx similarity index 74% rename from frontend/system/src/Admin/GS1Barcodes/GS1List.tsx rename to frontend/system/src/Admin/Barcodes/BarcodeList.tsx index 7e09d162f..b073e658a 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1List.tsx +++ b/frontend/system/src/Admin/Barcodes/BarcodeList.tsx @@ -18,42 +18,42 @@ import { useTableStore, } from '@common/ui'; import React from 'react'; -import { useDeleteGS1 } from './api'; -import { Gs1Fragment } from './api/operations.generated'; -import { GS1EditModal } from './GS1EditModal'; +import { useDeleteBarcode } from './api'; +import { BarcodeFragment } from './api/operations.generated'; +import { BarcodeEditModal } from './BarcodeEditModal'; import { getParentDescription } from './helpers'; -interface GS1ListProps { - gs1Barcodes: Omit[]; +interface BarcodeListProps { + barcodes: Omit[]; isError: boolean; isLoading: boolean; entityCodes?: string[]; - pagination?: TableProps['pagination']; + pagination?: TableProps['pagination']; updatePaginationQuery?: (page: number) => void; } -const GS1ListComponent = ({ - gs1Barcodes, +const BarcodeListComponent = ({ + barcodes, isError, isLoading, entityCodes, pagination, updatePaginationQuery, -}: GS1ListProps) => { +}: BarcodeListProps) => { const t = useTranslation('system'); - const { onOpen, onClose, isOpen } = useEditModal(); + const { onOpen, onClose, isOpen } = useEditModal(); - const { mutateAsync: deleteGS1 } = useDeleteGS1(); + const { mutateAsync: deleteGS1 } = useDeleteBarcode(); const selectedRows = useTableStore(state => Object.keys(state.rowState) .filter(id => state.rowState[id]?.isSelected) - .map(selectedId => gs1Barcodes.find(({ id }) => selectedId === id)) + .map(selectedId => barcodes.find(({ id }) => selectedId === id)) .filter(Boolean) ); - const columns = useColumns([ + const columns = useColumns([ { key: 'entity', label: 'label.product', @@ -87,7 +87,7 @@ const GS1ListComponent = ({ return ( <> {isOpen && ( - { + deleteItem={async (item: BarcodeFragment) => { await deleteGS1({ gtin: item.gtin }); }} /> @@ -117,10 +117,10 @@ const GS1ListComponent = ({ } + noDataElement={} pagination={pagination} onChangePage={updatePaginationQuery} /> @@ -128,10 +128,10 @@ const GS1ListComponent = ({ ); }; -export const GS1List = (props: GS1ListProps) => { +export const BarcodeList = (props: BarcodeListProps) => { return ( - + ); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx b/frontend/system/src/Admin/Barcodes/BarcodeListForEntityView.tsx similarity index 68% rename from frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx rename to frontend/system/src/Admin/Barcodes/BarcodeListForEntityView.tsx index 543db49b8..803df9991 100644 --- a/frontend/system/src/Admin/GS1Barcodes/GS1ListForEntityView.tsx +++ b/frontend/system/src/Admin/Barcodes/BarcodeListForEntityView.tsx @@ -1,18 +1,22 @@ import React, { useEffect } from 'react'; import { useBreadcrumbs } from '@common/hooks'; import { useParams } from 'react-router'; -import { GS1List } from './GS1List'; -import { useEntityWithGS1s } from './api'; +import { BarcodeList } from './BarcodeList'; +import { useEntityWithBarcodes } from './api'; import { AppBarContentPortal, Typography } from '@common/components'; import { useTranslation } from '@common/intl'; -import { getGS1Barcodes, getPackSizeCodes } from './helpers'; +import { getBarcodes, getPackSizeCodes } from './helpers'; -export const GS1ListForEntityView = () => { +export const BarcodeListForEntityView = () => { const t = useTranslation('system'); const { code } = useParams(); const { setSuffix } = useBreadcrumbs(); - const { data: entity, isError, isLoading } = useEntityWithGS1s(code ?? ''); + const { + data: entity, + isError, + isLoading, + } = useEntityWithBarcodes(code ?? ''); useEffect(() => { setSuffix(t('label.details')); @@ -20,7 +24,7 @@ export const GS1ListForEntityView = () => { if (!entity) return null; - const gs1Barcodes = getGS1Barcodes(entity); + const barcodes = getBarcodes(entity); const entityCodes = getPackSizeCodes(entity); return ( @@ -30,8 +34,8 @@ export const GS1ListForEntityView = () => { {entity.description} - { +export const BarcodeListView = () => { const { queryParams, updatePaginationQuery } = useQueryParamsState(); - const { data, isError, isLoading } = useGS1Barcodes(queryParams); + const { data, isError, isLoading } = useBarcodes(queryParams); - const gs1Barcodes = data?.data ?? []; + const barcodes = data?.data ?? []; const { page, first, offset } = queryParams; const pagination = { @@ -19,8 +19,8 @@ export const GS1ListView = () => { }; return ( - { +export const useAddBarcode = () => { const { client } = useGql(); const sdk = getSdk(client); const queryClient = useQueryClient(); const invalidateQueries = () => { - queryClient.invalidateQueries([GS1_BARCODES_KEY]); - queryClient.invalidateQueries([ENTITY_WITH_GS1S_KEY]); + queryClient.invalidateQueries([BARCODES_KEY]); + queryClient.invalidateQueries([ENTITY_WITH_BARCODES_KEY]); }; - return useMutation(sdk.AddGs1, { + return useMutation(sdk.AddBarcode, { onSettled: invalidateQueries, }); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts b/frontend/system/src/Admin/Barcodes/api/hooks/useBarcodes.ts similarity index 54% rename from frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts rename to frontend/system/src/Admin/Barcodes/api/hooks/useBarcodes.ts index 00e589685..594c92a23 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useGS1Barcodes.ts +++ b/frontend/system/src/Admin/Barcodes/api/hooks/useBarcodes.ts @@ -1,8 +1,8 @@ import { useGql, useQuery } from 'frontend/common/src'; -import { GS1_BARCODES_KEY } from '../../../../queryKeys'; +import { BARCODES_KEY } from '../../../../queryKeys'; import { getSdk } from '../operations.generated'; -export const useGS1Barcodes = ({ +export const useBarcodes = ({ first, offset, }: { @@ -11,10 +11,10 @@ export const useGS1Barcodes = ({ }) => { const { client } = useGql(); const sdk = getSdk(client); - const cacheKeys = [GS1_BARCODES_KEY, first, offset]; + const cacheKeys = [BARCODES_KEY, first, offset]; return useQuery(cacheKeys, async () => { - const response = await sdk.Gs1Barcodes({ first, offset }); - return response?.gs1Barcodes; + const response = await sdk.Barcodes({ first, offset }); + return response?.barcodes; }); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts b/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts similarity index 53% rename from frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts rename to frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts index f7650d79c..25fa65696 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useDeleteGS1.ts +++ b/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts @@ -1,13 +1,13 @@ import { useGql, useMutation, useQueryClient } from '@uc-frontend/common'; -import { GS1_BARCODES_KEY } from 'frontend/system/src/queryKeys'; +import { BARCODES_KEY } from 'frontend/system/src/queryKeys'; import { getSdk } from '../operations.generated'; -export const useDeleteGS1 = () => { +export const useDeleteBarcode = () => { const { client } = useGql(); const sdk = getSdk(client); const queryClient = useQueryClient(); - return useMutation(sdk.DeleteGS1, { - onSettled: () => queryClient.invalidateQueries(GS1_BARCODES_KEY), + return useMutation(sdk.DeleteBarcode, { + onSettled: () => queryClient.invalidateQueries(BARCODES_KEY), }); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts b/frontend/system/src/Admin/Barcodes/api/hooks/useEntityWithBarcode.ts similarity index 50% rename from frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts rename to frontend/system/src/Admin/Barcodes/api/hooks/useEntityWithBarcode.ts index 80d98fd95..be20c3ef8 100644 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/useEntityWithGS1s.ts +++ b/frontend/system/src/Admin/Barcodes/api/hooks/useEntityWithBarcode.ts @@ -1,15 +1,15 @@ import { useGql, useQuery } from '@uc-frontend/common'; -import { ENTITY_WITH_GS1S_KEY } from 'frontend/system/src/queryKeys'; +import { ENTITY_WITH_BARCODES_KEY } from 'frontend/system/src/queryKeys'; import { getSdk } from '../operations.generated'; -export const useEntityWithGS1s = (code: string) => { +export const useEntityWithBarcodes = (code: string) => { const { client } = useGql(); const sdk = getSdk(client); - const cacheKeys = [ENTITY_WITH_GS1S_KEY, code]; + const cacheKeys = [ENTITY_WITH_BARCODES_KEY, code]; return useQuery(cacheKeys, async () => { - const response = await sdk.entityWithGS1s({ code }); + const response = await sdk.entityWithBarcodes({ code }); return response?.entity; }); }; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/index.ts b/frontend/system/src/Admin/Barcodes/api/index.ts similarity index 100% rename from frontend/system/src/Admin/GS1Barcodes/api/index.ts rename to frontend/system/src/Admin/Barcodes/api/index.ts diff --git a/frontend/system/src/Admin/Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/Barcodes/api/operations.generated.ts new file mode 100644 index 000000000..fc2c73640 --- /dev/null +++ b/frontend/system/src/Admin/Barcodes/api/operations.generated.ts @@ -0,0 +1,123 @@ +import * as Types from '@uc-frontend/common'; + +import { GraphQLClient } from 'graphql-request'; +import * as Dom from 'graphql-request/dist/types.dom'; +import gql from 'graphql-tag'; +export type BarcodeFragment = { __typename?: 'BarcodeNode', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }; + +export type BarcodesQueryVariables = Types.Exact<{ + first?: Types.InputMaybe; + offset?: Types.InputMaybe; +}>; + + +export type BarcodesQuery = { __typename?: 'FullQuery', barcodes: { __typename?: 'BarcodeCollectionConnector', totalCount: number, data: Array<{ __typename?: 'BarcodeNode', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }> } }; + +export type AddBarcodeMutationVariables = Types.Exact<{ + input: Types.AddBarcodeInput; +}>; + + +export type AddBarcodeMutation = { __typename?: 'FullMutation', addBarcode: { __typename?: 'BarcodeNode', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } } }; + +export type DeleteBarcodeMutationVariables = Types.Exact<{ + gtin: Types.Scalars['String']['input']; +}>; + + +export type DeleteBarcodeMutation = { __typename?: 'FullMutation', deleteBarcode: number }; + +export type EntityWithBarcodesFragment = { __typename?: 'EntityType', code: string, name: string, description: string, type: string, barcodes: Array<{ __typename?: 'BarcodeType', id: string, gtin: string, manufacturer: string }> }; + +export type EntityWithBarcodesQueryVariables = Types.Exact<{ + code: Types.Scalars['String']['input']; +}>; + + +export type EntityWithBarcodesQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, description: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, description: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, description: string, type: string, barcodes: Array<{ __typename?: 'BarcodeType', id: string, gtin: string, manufacturer: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string, gtin: string, manufacturer: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string, gtin: string, manufacturer: string }> } | null }; + +export const BarcodeFragmentDoc = gql` + fragment Barcode on BarcodeNode { + id + gtin + manufacturer + entity { + code + name + description + } +} + `; +export const EntityWithBarcodesFragmentDoc = gql` + fragment EntityWithBarcodes on EntityType { + code + name + description + type + barcodes { + id + gtin + manufacturer + } +} + `; +export const BarcodesDocument = gql` + query Barcodes($first: Int, $offset: Int) { + barcodes(first: $first, offset: $offset) { + ... on BarcodeCollectionConnector { + data { + ...Barcode + } + totalCount + } + } +} + ${BarcodeFragmentDoc}`; +export const AddBarcodeDocument = gql` + mutation AddBarcode($input: AddBarcodeInput!) { + addBarcode(input: $input) { + ...Barcode + } +} + ${BarcodeFragmentDoc}`; +export const DeleteBarcodeDocument = gql` + mutation DeleteBarcode($gtin: String!) { + deleteBarcode(gtin: $gtin) +} + `; +export const EntityWithBarcodesDocument = gql` + query entityWithBarcodes($code: String!) { + entity(code: $code) { + ...EntityWithBarcodes + children { + ...EntityWithBarcodes + children { + ...EntityWithBarcodes + } + } + } +} + ${EntityWithBarcodesFragmentDoc}`; + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + Barcodes(variables?: BarcodesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(BarcodesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'Barcodes', 'query'); + }, + AddBarcode(variables: AddBarcodeMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(AddBarcodeDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddBarcode', 'mutation'); + }, + DeleteBarcode(variables: DeleteBarcodeMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(DeleteBarcodeDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'DeleteBarcode', 'mutation'); + }, + entityWithBarcodes(variables: EntityWithBarcodesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(EntityWithBarcodesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'entityWithBarcodes', 'query'); + } + }; +} +export type Sdk = ReturnType; \ No newline at end of file diff --git a/frontend/system/src/Admin/Barcodes/api/operations.graphql b/frontend/system/src/Admin/Barcodes/api/operations.graphql new file mode 100644 index 000000000..ee06b0a44 --- /dev/null +++ b/frontend/system/src/Admin/Barcodes/api/operations.graphql @@ -0,0 +1,55 @@ +fragment Barcode on BarcodeNode { + id + gtin + manufacturer + entity { + code + name + description + } +} + +query Barcodes($first: Int, $offset: Int) { + barcodes(first: $first, offset: $offset) { + ... on BarcodeCollectionConnector { + data { + ...Barcode + } + totalCount + } + } +} + +mutation AddBarcode($input: AddBarcodeInput!) { + addBarcode(input: $input) { + ...Barcode + } +} + +mutation DeleteBarcode($gtin: String!) { + deleteBarcode(gtin: $gtin) +} + +fragment EntityWithBarcodes on EntityType { + code + name + description + type + barcodes { + id + gtin + manufacturer + } +} + +query entityWithBarcodes($code: String!) { + entity(code: $code) { + ...EntityWithBarcodes + children { + ...EntityWithBarcodes + children { + ...EntityWithBarcodes + } + } + } +} diff --git a/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts b/frontend/system/src/Admin/Barcodes/helpers.test.ts similarity index 92% rename from frontend/system/src/Admin/GS1Barcodes/helpers.test.ts rename to frontend/system/src/Admin/Barcodes/helpers.test.ts index 7d0b98a95..babd92de3 100644 --- a/frontend/system/src/Admin/GS1Barcodes/helpers.test.ts +++ b/frontend/system/src/Admin/Barcodes/helpers.test.ts @@ -2,8 +2,8 @@ import { EntityType } from '../../constants'; import { getParentDescription, getPackSizeCodes, - EntityWithGs1Details, - getGS1Barcodes, + EntityWithBarcodes, + getBarcodes, } from './helpers'; describe('getParentDescription', () => { @@ -34,7 +34,7 @@ describe('getParentDescription', () => { describe('getPackSizeCodes', () => { it('returns an empty array when entity type is not PackSize', () => { - const entity: EntityWithGs1Details = { + const entity: EntityWithBarcodes = { type: EntityType.ActiveIngredients, code: '123', name: '123', @@ -64,7 +64,7 @@ describe('getPackSizeCodes', () => { }); it('returns an array with the entity code which entity type is PackSize', () => { - const entity: EntityWithGs1Details = { + const entity: EntityWithBarcodes = { type: EntityType.PackSize, code: '123', name: '123', @@ -76,7 +76,7 @@ describe('getPackSizeCodes', () => { }); it('returns an array with the child entity codes where entity type is PackSize', () => { - const entity: EntityWithGs1Details = { + const entity: EntityWithBarcodes = { type: EntityType.ImmediatePackaging, code: '123', name: '123', @@ -106,7 +106,7 @@ describe('getPackSizeCodes', () => { describe('getGS1Barcodes', () => { it('returns an empty array when entity has no GS1 barcodes', () => { - const entity: EntityWithGs1Details = { + const entity: EntityWithBarcodes = { type: EntityType.Unit, code: '123', name: '123', @@ -114,11 +114,11 @@ describe('getGS1Barcodes', () => { gs1Barcodes: [], }; - expect(getGS1Barcodes(entity)).toEqual([]); + expect(getBarcodes(entity)).toEqual([]); }); it('returns an array with the entity GS1 barcodes', () => { - const entity: EntityWithGs1Details = { + const entity: EntityWithBarcodes = { type: EntityType.PackSize, code: '123', name: '123', @@ -129,7 +129,7 @@ describe('getGS1Barcodes', () => { ], }; - expect(getGS1Barcodes(entity)).toEqual([ + expect(getBarcodes(entity)).toEqual([ { id: '1234567890', gtin: '1234567890', manufacturer: 'X', entity }, { id: '0987654321', gtin: '0987654321', manufacturer: 'Y', entity }, ]); @@ -166,7 +166,7 @@ describe('getGS1Barcodes', () => { ], }; - expect(getGS1Barcodes(entity)).toEqual([ + expect(getBarcodes(entity)).toEqual([ { id: '1234567890', gtin: '1234567890', diff --git a/frontend/system/src/Admin/GS1Barcodes/helpers.ts b/frontend/system/src/Admin/Barcodes/helpers.ts similarity index 56% rename from frontend/system/src/Admin/GS1Barcodes/helpers.ts rename to frontend/system/src/Admin/Barcodes/helpers.ts index c783c6669..824e03bea 100644 --- a/frontend/system/src/Admin/GS1Barcodes/helpers.ts +++ b/frontend/system/src/Admin/Barcodes/helpers.ts @@ -1,11 +1,11 @@ import { EntityType } from '../../constants'; import { - EntityWithGs1sFragment, - Gs1Fragment, + BarcodeFragment, + EntityWithBarcodesFragment, } from './api/operations.generated'; -export type EntityWithGs1Details = EntityWithGs1sFragment & { - children?: EntityWithGs1Details[]; +export type EntityWithBarcodes = EntityWithBarcodesFragment & { + children?: EntityWithBarcodes[]; }; export const getParentDescription = ({ @@ -20,7 +20,7 @@ export const getParentDescription = ({ return description.substring(0, nameIndex).trim(); }; -export const getPackSizeCodes = (entity?: EntityWithGs1Details | null) => { +export const getPackSizeCodes = (entity?: EntityWithBarcodes | null) => { const packSizeCodes: string[] = []; if (!entity) return packSizeCodes; @@ -29,7 +29,7 @@ export const getPackSizeCodes = (entity?: EntityWithGs1Details | null) => { packSizeCodes.push(entity.code); } - const addChildCodes = (e: EntityWithGs1Details) => { + const addChildCodes = (e: EntityWithBarcodes) => { e.children?.forEach(c => { if (c.type === EntityType.PackSize) { packSizeCodes.push(c.code); @@ -42,16 +42,16 @@ export const getPackSizeCodes = (entity?: EntityWithGs1Details | null) => { return packSizeCodes; }; -// GS1s for entity and all its children (though there should only be GS1s on PackSize entities, the lowest level...) -export const getGS1Barcodes = (entity: EntityWithGs1Details) => { - const barcodes: Omit[] = []; +// Barcodes for entity and all its children (though there should only be barcodes on PackSize entities, the lowest level...) +export const getBarcodes = (entity: EntityWithBarcodes) => { + const barcodes: Omit[] = []; - const addBarcodes = (e: EntityWithGs1Details) => - e.gs1Barcodes.forEach(b => barcodes.push({ ...b, entity: e })); + const addBarcodes = (e: EntityWithBarcodes) => + e.barcodes.forEach(b => barcodes.push({ ...b, entity: e })); addBarcodes(entity); - const addChildBarcodes = (e: EntityWithGs1Details) => { + const addChildBarcodes = (e: EntityWithBarcodes) => { e.children?.forEach(c => { addBarcodes(c); addChildBarcodes(c); diff --git a/frontend/system/src/Admin/Barcodes/index.ts b/frontend/system/src/Admin/Barcodes/index.ts new file mode 100644 index 000000000..b96ae04d1 --- /dev/null +++ b/frontend/system/src/Admin/Barcodes/index.ts @@ -0,0 +1,2 @@ +export * from './BarcodeListView'; +export * from './BarcodeListForEntityView'; diff --git a/frontend/system/src/Admin/EditEntity/helpers.ts b/frontend/system/src/Admin/EditEntity/helpers.ts index ea0d0bc1a..e85908812 100644 --- a/frontend/system/src/Admin/EditEntity/helpers.ts +++ b/frontend/system/src/Admin/EditEntity/helpers.ts @@ -538,7 +538,7 @@ export const buildEntityDetailsFromPendingChangeBody = ( name: input.name || '', type: input.type || '', alternativeNames: input.alternativeNames || [], - gs1Barcodes: [], + barcodes: [], properties: input.properties?.map(p => ({ code: p.code, diff --git a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts b/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts deleted file mode 100644 index 4c6bc5065..000000000 --- a/frontend/system/src/Admin/GS1Barcodes/api/hooks/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './useGS1Barcodes'; -export * from './useAddGS1'; -export * from './useDeleteGS1'; -export * from './useEntityWithGS1s'; diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts b/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts deleted file mode 100644 index 1b673d730..000000000 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.generated.ts +++ /dev/null @@ -1,123 +0,0 @@ -import * as Types from '@uc-frontend/common'; - -import { GraphQLClient } from 'graphql-request'; -import * as Dom from 'graphql-request/dist/types.dom'; -import gql from 'graphql-tag'; -export type Gs1Fragment = { __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }; - -export type Gs1BarcodesQueryVariables = Types.Exact<{ - first?: Types.InputMaybe; - offset?: Types.InputMaybe; -}>; - - -export type Gs1BarcodesQuery = { __typename?: 'FullQuery', gs1Barcodes: { __typename?: 'Gs1CollectionConnector', totalCount: number, data: Array<{ __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } }> } }; - -export type AddGs1MutationVariables = Types.Exact<{ - input: Types.AddGs1Input; -}>; - - -export type AddGs1Mutation = { __typename?: 'FullMutation', addGs1: { __typename?: 'Gs1Node', id: string, gtin: string, manufacturer: string, entity: { __typename?: 'EntityType', code: string, name: string, description: string } } }; - -export type DeleteGs1MutationVariables = Types.Exact<{ - gtin: Types.Scalars['String']['input']; -}>; - - -export type DeleteGs1Mutation = { __typename?: 'FullMutation', deleteGs1: number }; - -export type EntityWithGs1sFragment = { __typename?: 'EntityType', code: string, name: string, description: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> }; - -export type EntityWithGs1sQueryVariables = Types.Exact<{ - code: Types.Scalars['String']['input']; -}>; - - -export type EntityWithGs1sQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, description: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, description: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, description: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }> } | null }; - -export const Gs1FragmentDoc = gql` - fragment GS1 on Gs1Node { - id - gtin - manufacturer - entity { - code - name - description - } -} - `; -export const EntityWithGs1sFragmentDoc = gql` - fragment EntityWithGS1s on EntityType { - code - name - description - type - gs1Barcodes { - id - gtin - manufacturer - } -} - `; -export const Gs1BarcodesDocument = gql` - query Gs1Barcodes($first: Int, $offset: Int) { - gs1Barcodes(first: $first, offset: $offset) { - ... on Gs1CollectionConnector { - data { - ...GS1 - } - totalCount - } - } -} - ${Gs1FragmentDoc}`; -export const AddGs1Document = gql` - mutation AddGs1($input: AddGS1Input!) { - addGs1(input: $input) { - ...GS1 - } -} - ${Gs1FragmentDoc}`; -export const DeleteGs1Document = gql` - mutation DeleteGS1($gtin: String!) { - deleteGs1(gtin: $gtin) -} - `; -export const EntityWithGs1sDocument = gql` - query entityWithGS1s($code: String!) { - entity(code: $code) { - ...EntityWithGS1s - children { - ...EntityWithGS1s - children { - ...EntityWithGS1s - } - } - } -} - ${EntityWithGs1sFragmentDoc}`; - -export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; - - -const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action(); - -export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { - return { - Gs1Barcodes(variables?: Gs1BarcodesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { - return withWrapper((wrappedRequestHeaders) => client.request(Gs1BarcodesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'Gs1Barcodes', 'query'); - }, - AddGs1(variables: AddGs1MutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { - return withWrapper((wrappedRequestHeaders) => client.request(AddGs1Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'AddGs1', 'mutation'); - }, - DeleteGS1(variables: DeleteGs1MutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { - return withWrapper((wrappedRequestHeaders) => client.request(DeleteGs1Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'DeleteGS1', 'mutation'); - }, - entityWithGS1s(variables: EntityWithGs1sQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { - return withWrapper((wrappedRequestHeaders) => client.request(EntityWithGs1sDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'entityWithGS1s', 'query'); - } - }; -} -export type Sdk = ReturnType; \ No newline at end of file diff --git a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql b/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql deleted file mode 100644 index 7f8a58c98..000000000 --- a/frontend/system/src/Admin/GS1Barcodes/api/operations.graphql +++ /dev/null @@ -1,55 +0,0 @@ -fragment GS1 on Gs1Node { - id - gtin - manufacturer - entity { - code - name - description - } -} - -query Gs1Barcodes($first: Int, $offset: Int) { - gs1Barcodes(first: $first, offset: $offset) { - ... on Gs1CollectionConnector { - data { - ...GS1 - } - totalCount - } - } -} - -mutation AddGs1($input: AddGS1Input!) { - addGs1(input: $input) { - ...GS1 - } -} - -mutation DeleteGS1($gtin: String!) { - deleteGs1(gtin: $gtin) -} - -fragment EntityWithGS1s on EntityType { - code - name - description - type - gs1Barcodes { - id - gtin - manufacturer - } -} - -query entityWithGS1s($code: String!) { - entity(code: $code) { - ...EntityWithGS1s - children { - ...EntityWithGS1s - children { - ...EntityWithGS1s - } - } - } -} diff --git a/frontend/system/src/Admin/GS1Barcodes/index.ts b/frontend/system/src/Admin/GS1Barcodes/index.ts deleted file mode 100644 index a909abcf1..000000000 --- a/frontend/system/src/Admin/GS1Barcodes/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './GS1ListView'; diff --git a/frontend/system/src/Admin/Service.tsx b/frontend/system/src/Admin/Service.tsx index 48e38ac89..236634a3f 100644 --- a/frontend/system/src/Admin/Service.tsx +++ b/frontend/system/src/Admin/Service.tsx @@ -6,8 +6,7 @@ import { UserAccountListView } from './Users/ListView'; import { ConfigurationTabsView } from './Configuration'; import { DrugInteractionsView } from './Interactions'; import { PendingChangeDetails, PendingChangesListView } from './PendingChanges'; -import { GS1ListView } from './GS1Barcodes'; -import { GS1ListForEntityView } from './GS1Barcodes/GS1ListForEntityView'; +import { BarcodeListView, BarcodeListForEntityView } from './Barcodes'; const AdminService = () => { return ( @@ -34,10 +33,10 @@ const AdminService = () => { path={`/${AppRoute.PendingChanges}/:id`} element={} /> - } /> + } /> } + path={`/${AppRoute.Barcodes}/:code`} + element={} /> { +export const BarcodeLink = ({ entity }: { entity: EntityData }) => { const t = useTranslation('system'); - if (!SHOW_GS1_LINK_TYPES.includes(entity.type as EntityType)) return <>; + if (!SHOW_BARCODE_LINK_TYPES.includes(entity.type as EntityType)) + return <>; const barcodeCount = useMemo(() => getBarcodeCount(entity), [entity]); @@ -25,11 +26,11 @@ export const GS1Link = ({ entity }: { entity: EntityData }) => { - {t('label.linked-gs1s', { count: barcodeCount })} + {t('label.related-barcodes', { count: barcodeCount })} )} @@ -39,11 +40,11 @@ export const GS1Link = ({ entity }: { entity: EntityData }) => { const getBarcodeCount = (entity: EntityData) => { let count = 0; - count += entity.gs1Barcodes.length; + count += entity.barcodes.length; const countChildBarcodes = (e: EntityData) => { e.children?.forEach(c => { - count += c.gs1Barcodes.length; + count += c.barcodes.length; countChildBarcodes(c); }); }; diff --git a/frontend/system/src/Entities/EntityTreeItem.tsx b/frontend/system/src/Entities/EntityTreeItem.tsx index 5634e72a1..98dde094f 100644 --- a/frontend/system/src/Entities/EntityTreeItem.tsx +++ b/frontend/system/src/Entities/EntityTreeItem.tsx @@ -7,7 +7,7 @@ import { TreeItem } from '@mui/lab'; import { usePropertyConfigurationItems } from '../Admin/Configuration/api/hooks/usePropertyConfigurationItems'; import { EntityType } from '../constants'; import { EntityData } from './EntityData'; -import { GS1Link } from './GS1Link'; +import { BarcodeLink } from './BarcodeLink'; export const EntityTreeItem = ({ entity, @@ -91,7 +91,7 @@ export const EntityTreeItem = ({ )} - + } > diff --git a/frontend/system/src/Entities/api/operations.generated.ts b/frontend/system/src/Entities/api/operations.generated.ts index 08bb93ed6..059cecc04 100644 --- a/frontend/system/src/Entities/api/operations.generated.ts +++ b/frontend/system/src/Entities/api/operations.generated.ts @@ -5,7 +5,7 @@ import * as Dom from 'graphql-request/dist/types.dom'; import gql from 'graphql-tag'; export type EntityRowFragment = { __typename?: 'EntityType', type: string, description: string, code: string, name: string, id: string, alternativeNames: Array<{ __typename?: 'AlternativeNameType', name: string }> }; -export type EntityDetailsFragment = { __typename?: 'EntityType', code: string, name: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }; +export type EntityDetailsFragment = { __typename?: 'EntityType', code: string, name: string, type: string, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }; export type EntitiesQueryVariables = Types.Exact<{ filter: Types.EntitySearchInput; @@ -21,14 +21,14 @@ export type EntityQueryVariables = Types.Exact<{ }>; -export type EntityQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; +export type EntityQuery = { __typename?: 'FullQuery', entity?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; export type ProductQueryVariables = Types.Exact<{ code: Types.Scalars['String']['input']; }>; -export type ProductQuery = { __typename?: 'FullQuery', product?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, gs1Barcodes: Array<{ __typename?: 'Gs1Type', id: string, gtin: string, manufacturer: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; +export type ProductQuery = { __typename?: 'FullQuery', product?: { __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, children: Array<{ __typename?: 'EntityType', code: string, name: string, type: string, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> }>, barcodes: Array<{ __typename?: 'BarcodeType', id: string }>, alternativeNames: Array<{ __typename?: 'AlternativeNameType', code: string, name: string }>, properties: Array<{ __typename?: 'PropertiesType', code: string, type: string, value: string }> } | null }; export const EntityRowFragmentDoc = gql` fragment EntityRow on EntityType { @@ -47,10 +47,8 @@ export const EntityDetailsFragmentDoc = gql` code name type - gs1Barcodes { + barcodes { id - gtin - manufacturer } alternativeNames { code diff --git a/frontend/system/src/Entities/api/operations.graphql b/frontend/system/src/Entities/api/operations.graphql index 4332d480a..f9715ead0 100644 --- a/frontend/system/src/Entities/api/operations.graphql +++ b/frontend/system/src/Entities/api/operations.graphql @@ -13,7 +13,7 @@ fragment EntityDetails on EntityType { code name type - gs1Barcodes { + barcodes { id } alternativeNames { diff --git a/frontend/system/src/queryKeys.ts b/frontend/system/src/queryKeys.ts index b506ee162..8a7bf42e4 100644 --- a/frontend/system/src/queryKeys.ts +++ b/frontend/system/src/queryKeys.ts @@ -3,5 +3,5 @@ export const ENTITIES_KEY = 'ENTITIES'; export const PENDING_CHANGE_KEY = 'PENDING_CHANGE'; export const PENDING_CHANGES_KEY = 'PENDING_CHANGES'; export const PROPERTY_CONFIG_ITEMS_KEY = 'PROPERTY_CONFIG_ITEMS'; -export const GS1_BARCODES_KEY = 'GS1_BARCODES'; -export const ENTITY_WITH_GS1S_KEY = 'ENTITY_WITH_GS1S'; +export const BARCODES_KEY = 'BARCODES'; +export const ENTITY_WITH_BARCODES_KEY = 'ENTITY_WITH_BARCODES'; From 3d50235a6c50aa400f0173c474ecfccf6136e5b3 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 12:33:51 +1300 Subject: [PATCH 25/38] invalidate entity barcodes on delete --- .../src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts b/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts index 25fa65696..400b12302 100644 --- a/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts +++ b/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts @@ -1,5 +1,8 @@ import { useGql, useMutation, useQueryClient } from '@uc-frontend/common'; -import { BARCODES_KEY } from 'frontend/system/src/queryKeys'; +import { + BARCODES_KEY, + ENTITY_WITH_BARCODES_KEY, +} from 'frontend/system/src/queryKeys'; import { getSdk } from '../operations.generated'; export const useDeleteBarcode = () => { @@ -8,6 +11,9 @@ export const useDeleteBarcode = () => { const queryClient = useQueryClient(); return useMutation(sdk.DeleteBarcode, { - onSettled: () => queryClient.invalidateQueries(BARCODES_KEY), + onSettled: () => { + queryClient.invalidateQueries(BARCODES_KEY); + queryClient.invalidateQueries(ENTITY_WITH_BARCODES_KEY); + }, }); }; From b3abf0ee5c2b3c31a153f9163e2ef8a6d0c979dc Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 12:37:27 +1300 Subject: [PATCH 26/38] remove test gs1 reference --- .../system/src/Admin/Barcodes/helpers.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/system/src/Admin/Barcodes/helpers.test.ts b/frontend/system/src/Admin/Barcodes/helpers.test.ts index babd92de3..f5aa315b5 100644 --- a/frontend/system/src/Admin/Barcodes/helpers.test.ts +++ b/frontend/system/src/Admin/Barcodes/helpers.test.ts @@ -39,21 +39,21 @@ describe('getPackSizeCodes', () => { code: '123', name: '123', description: '123', - gs1Barcodes: [], + barcodes: [], children: [ { type: EntityType.Brand, code: '456', name: '456', description: '456', - gs1Barcodes: [], + barcodes: [], children: [ { type: EntityType.Strength, code: '789', name: '789', description: '789', - gs1Barcodes: [], + barcodes: [], }, ], }, @@ -69,7 +69,7 @@ describe('getPackSizeCodes', () => { code: '123', name: '123', description: '123', - gs1Barcodes: [], + barcodes: [], }; expect(getPackSizeCodes(entity)).toEqual(['123']); @@ -81,21 +81,21 @@ describe('getPackSizeCodes', () => { code: '123', name: '123', description: '123', - gs1Barcodes: [], + barcodes: [], children: [ { type: EntityType.PackSize, code: '456', name: '456', description: '456', - gs1Barcodes: [], + barcodes: [], }, { type: EntityType.PackSize, code: '789', name: '789', description: '789', - gs1Barcodes: [], + barcodes: [], }, ], }; @@ -111,7 +111,7 @@ describe('getGS1Barcodes', () => { code: '123', name: '123', description: '123', - gs1Barcodes: [], + barcodes: [], }; expect(getBarcodes(entity)).toEqual([]); @@ -123,7 +123,7 @@ describe('getGS1Barcodes', () => { code: '123', name: '123', description: '123', - gs1Barcodes: [ + barcodes: [ { id: '1234567890', gtin: '1234567890', manufacturer: 'X' }, { id: '0987654321', gtin: '0987654321', manufacturer: 'Y' }, ], @@ -141,14 +141,14 @@ describe('getGS1Barcodes', () => { code: '123', name: '123', description: '123', - gs1Barcodes: [], + barcodes: [], children: [ { type: EntityType.PackSize, code: '456', name: '456', description: '456', - gs1Barcodes: [ + barcodes: [ { id: '1234567890', gtin: '1234567890', manufacturer: 'X' }, { id: '0987654321', gtin: '0987654321', manufacturer: 'Y' }, ], @@ -158,7 +158,7 @@ describe('getGS1Barcodes', () => { code: '789', name: '789', description: '789', - gs1Barcodes: [ + barcodes: [ { id: '1111111111', gtin: '1111111111', manufacturer: 'X' }, { id: '2222222222', gtin: '2222222222', manufacturer: 'Y' }, ], From 4bf82d9c9733130ca8b1a540d0161f65aaf20fbd Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 12:38:40 +1300 Subject: [PATCH 27/38] fix comments --- frontend/system/src/Admin/Barcodes/helpers.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/system/src/Admin/Barcodes/helpers.test.ts b/frontend/system/src/Admin/Barcodes/helpers.test.ts index f5aa315b5..952cd165d 100644 --- a/frontend/system/src/Admin/Barcodes/helpers.test.ts +++ b/frontend/system/src/Admin/Barcodes/helpers.test.ts @@ -104,8 +104,8 @@ describe('getPackSizeCodes', () => { }); }); -describe('getGS1Barcodes', () => { - it('returns an empty array when entity has no GS1 barcodes', () => { +describe('getBarcodes', () => { + it('returns an empty array when entity has no barcodes', () => { const entity: EntityWithBarcodes = { type: EntityType.Unit, code: '123', @@ -117,7 +117,7 @@ describe('getGS1Barcodes', () => { expect(getBarcodes(entity)).toEqual([]); }); - it('returns an array with the entity GS1 barcodes', () => { + it('returns an array with the entity barcodes', () => { const entity: EntityWithBarcodes = { type: EntityType.PackSize, code: '123', @@ -135,7 +135,7 @@ describe('getGS1Barcodes', () => { ]); }); - it('returns an array with the entity and child GS1 barcodes', () => { + it('returns an array with the entity and child barcodes', () => { const entity = { type: EntityType.ImmediatePackaging, code: '123', From 27749151efc29ed518109d9ea495e8ac7caa32ef Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 17:07:21 +1300 Subject: [PATCH 28/38] move barcodes to within browse section --- frontend/host/src/components/AppDrawer/AppDrawer.tsx | 2 +- frontend/system/src/Admin/Service.tsx | 6 ------ .../src/{Admin => Entities}/Barcodes/BarcodeEditModal.tsx | 0 .../src/{Admin => Entities}/Barcodes/BarcodeList.tsx | 0 .../Barcodes/BarcodeListForEntityView.tsx | 0 .../src/{Admin => Entities}/Barcodes/BarcodeListView.tsx | 0 .../src/{Admin => Entities}/Barcodes/api/hooks/index.ts | 0 .../Barcodes/api/hooks/useAddBarcode.ts | 0 .../{Admin => Entities}/Barcodes/api/hooks/useBarcodes.ts | 0 .../Barcodes/api/hooks/useDeleteBarcode.ts | 0 .../Barcodes/api/hooks/useEntityWithBarcode.ts | 0 .../system/src/{Admin => Entities}/Barcodes/api/index.ts | 0 .../Barcodes/api/operations.generated.ts | 0 .../{Admin => Entities}/Barcodes/api/operations.graphql | 0 .../src/{Admin => Entities}/Barcodes/helpers.test.ts | 0 .../system/src/{Admin => Entities}/Barcodes/helpers.ts | 0 frontend/system/src/{Admin => Entities}/Barcodes/index.ts | 0 frontend/system/src/Entities/Service.tsx | 7 +++++++ 18 files changed, 8 insertions(+), 7 deletions(-) rename frontend/system/src/{Admin => Entities}/Barcodes/BarcodeEditModal.tsx (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/BarcodeList.tsx (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/BarcodeListForEntityView.tsx (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/BarcodeListView.tsx (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/hooks/index.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/hooks/useAddBarcode.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/hooks/useBarcodes.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/hooks/useDeleteBarcode.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/hooks/useEntityWithBarcode.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/index.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/operations.generated.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/api/operations.graphql (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/helpers.test.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/helpers.ts (100%) rename frontend/system/src/{Admin => Entities}/Barcodes/index.ts (100%) diff --git a/frontend/host/src/components/AppDrawer/AppDrawer.tsx b/frontend/host/src/components/AppDrawer/AppDrawer.tsx index 85c70b094..b905cb70f 100644 --- a/frontend/host/src/components/AppDrawer/AppDrawer.tsx +++ b/frontend/host/src/components/AppDrawer/AppDrawer.tsx @@ -206,7 +206,7 @@ export const AppDrawer: React.FC = () => { text={t('pending-changes')} /> } diff --git a/frontend/system/src/Admin/Service.tsx b/frontend/system/src/Admin/Service.tsx index 236634a3f..b825eac12 100644 --- a/frontend/system/src/Admin/Service.tsx +++ b/frontend/system/src/Admin/Service.tsx @@ -6,7 +6,6 @@ import { UserAccountListView } from './Users/ListView'; import { ConfigurationTabsView } from './Configuration'; import { DrugInteractionsView } from './Interactions'; import { PendingChangeDetails, PendingChangesListView } from './PendingChanges'; -import { BarcodeListView, BarcodeListForEntityView } from './Barcodes'; const AdminService = () => { return ( @@ -33,11 +32,6 @@ const AdminService = () => { path={`/${AppRoute.PendingChanges}/:id`} element={} /> - } /> - } - /> } diff --git a/frontend/system/src/Admin/Barcodes/BarcodeEditModal.tsx b/frontend/system/src/Entities/Barcodes/BarcodeEditModal.tsx similarity index 100% rename from frontend/system/src/Admin/Barcodes/BarcodeEditModal.tsx rename to frontend/system/src/Entities/Barcodes/BarcodeEditModal.tsx diff --git a/frontend/system/src/Admin/Barcodes/BarcodeList.tsx b/frontend/system/src/Entities/Barcodes/BarcodeList.tsx similarity index 100% rename from frontend/system/src/Admin/Barcodes/BarcodeList.tsx rename to frontend/system/src/Entities/Barcodes/BarcodeList.tsx diff --git a/frontend/system/src/Admin/Barcodes/BarcodeListForEntityView.tsx b/frontend/system/src/Entities/Barcodes/BarcodeListForEntityView.tsx similarity index 100% rename from frontend/system/src/Admin/Barcodes/BarcodeListForEntityView.tsx rename to frontend/system/src/Entities/Barcodes/BarcodeListForEntityView.tsx diff --git a/frontend/system/src/Admin/Barcodes/BarcodeListView.tsx b/frontend/system/src/Entities/Barcodes/BarcodeListView.tsx similarity index 100% rename from frontend/system/src/Admin/Barcodes/BarcodeListView.tsx rename to frontend/system/src/Entities/Barcodes/BarcodeListView.tsx diff --git a/frontend/system/src/Admin/Barcodes/api/hooks/index.ts b/frontend/system/src/Entities/Barcodes/api/hooks/index.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/hooks/index.ts rename to frontend/system/src/Entities/Barcodes/api/hooks/index.ts diff --git a/frontend/system/src/Admin/Barcodes/api/hooks/useAddBarcode.ts b/frontend/system/src/Entities/Barcodes/api/hooks/useAddBarcode.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/hooks/useAddBarcode.ts rename to frontend/system/src/Entities/Barcodes/api/hooks/useAddBarcode.ts diff --git a/frontend/system/src/Admin/Barcodes/api/hooks/useBarcodes.ts b/frontend/system/src/Entities/Barcodes/api/hooks/useBarcodes.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/hooks/useBarcodes.ts rename to frontend/system/src/Entities/Barcodes/api/hooks/useBarcodes.ts diff --git a/frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts b/frontend/system/src/Entities/Barcodes/api/hooks/useDeleteBarcode.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/hooks/useDeleteBarcode.ts rename to frontend/system/src/Entities/Barcodes/api/hooks/useDeleteBarcode.ts diff --git a/frontend/system/src/Admin/Barcodes/api/hooks/useEntityWithBarcode.ts b/frontend/system/src/Entities/Barcodes/api/hooks/useEntityWithBarcode.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/hooks/useEntityWithBarcode.ts rename to frontend/system/src/Entities/Barcodes/api/hooks/useEntityWithBarcode.ts diff --git a/frontend/system/src/Admin/Barcodes/api/index.ts b/frontend/system/src/Entities/Barcodes/api/index.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/index.ts rename to frontend/system/src/Entities/Barcodes/api/index.ts diff --git a/frontend/system/src/Admin/Barcodes/api/operations.generated.ts b/frontend/system/src/Entities/Barcodes/api/operations.generated.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/operations.generated.ts rename to frontend/system/src/Entities/Barcodes/api/operations.generated.ts diff --git a/frontend/system/src/Admin/Barcodes/api/operations.graphql b/frontend/system/src/Entities/Barcodes/api/operations.graphql similarity index 100% rename from frontend/system/src/Admin/Barcodes/api/operations.graphql rename to frontend/system/src/Entities/Barcodes/api/operations.graphql diff --git a/frontend/system/src/Admin/Barcodes/helpers.test.ts b/frontend/system/src/Entities/Barcodes/helpers.test.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/helpers.test.ts rename to frontend/system/src/Entities/Barcodes/helpers.test.ts diff --git a/frontend/system/src/Admin/Barcodes/helpers.ts b/frontend/system/src/Entities/Barcodes/helpers.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/helpers.ts rename to frontend/system/src/Entities/Barcodes/helpers.ts diff --git a/frontend/system/src/Admin/Barcodes/index.ts b/frontend/system/src/Entities/Barcodes/index.ts similarity index 100% rename from frontend/system/src/Admin/Barcodes/index.ts rename to frontend/system/src/Entities/Barcodes/index.ts diff --git a/frontend/system/src/Entities/Service.tsx b/frontend/system/src/Entities/Service.tsx index 3125dd3af..c31aec8c6 100644 --- a/frontend/system/src/Entities/Service.tsx +++ b/frontend/system/src/Entities/Service.tsx @@ -2,11 +2,18 @@ import React from 'react'; import { Routes, Route } from '@uc-frontend/common'; import { ListView } from './ListView'; import { EntityDetails } from './EntityDetails'; +import { BarcodeListView, BarcodeListForEntityView } from './Barcodes'; +import { AppRoute } from 'frontend/config/src'; const EntitiesService = () => { return ( } /> + } /> + } + /> } /> ); From 861b2eab381da0b1b8a4dbab38f7be4a09f0aeb2 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Thu, 1 Feb 2024 17:22:06 +1300 Subject: [PATCH 29/38] add barcodes to app bar links --- frontend/common/src/intl/locales/en/system.json | 1 + frontend/host/src/components/AppBar/AppBar.tsx | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/frontend/common/src/intl/locales/en/system.json b/frontend/common/src/intl/locales/en/system.json index e6210d4a0..c7ab4e478 100644 --- a/frontend/common/src/intl/locales/en/system.json +++ b/frontend/common/src/intl/locales/en/system.json @@ -51,6 +51,7 @@ "label.approve-next": "Approve & Next", "label.alt-name": "Alternative Name", "label.alt-names": "Alternative Names", + "label.barcodes": "Barcodes", "label.brand": "Brand", "label.brands": "Brands", "label.browse": "Browse", diff --git a/frontend/host/src/components/AppBar/AppBar.tsx b/frontend/host/src/components/AppBar/AppBar.tsx index b33b5f055..9b72ccc2b 100644 --- a/frontend/host/src/components/AppBar/AppBar.tsx +++ b/frontend/host/src/components/AppBar/AppBar.tsx @@ -81,6 +81,15 @@ const NavLinks = () => { {t('label.browse')} | + + {t('label.barcodes')} + + | Date: Thu, 1 Feb 2024 17:22:29 +1300 Subject: [PATCH 30/38] hide admin components when unauthenticated --- frontend/system/src/Entities/BarcodeLink.tsx | 2 +- .../src/Entities/Barcodes/BarcodeList.tsx | 70 +++++++++++-------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/frontend/system/src/Entities/BarcodeLink.tsx b/frontend/system/src/Entities/BarcodeLink.tsx index 0a89cdd9a..080228f73 100644 --- a/frontend/system/src/Entities/BarcodeLink.tsx +++ b/frontend/system/src/Entities/BarcodeLink.tsx @@ -25,7 +25,7 @@ export const BarcodeLink = ({ entity }: { entity: EntityData }) => { {!!barcodeCount && ( { const t = useTranslation('system'); + const { hasPermission } = useAuthContext(); + const isAdmin = hasPermission(PermissionNode.ServerAdmin); const { onOpen, onClose, isOpen } = useEditModal(); @@ -53,7 +58,7 @@ const BarcodeListComponent = ({ .filter(Boolean) ); - const columns = useColumns([ + const columnDefs: ColumnDescription[] = [ { key: 'entity', label: 'label.product', @@ -81,39 +86,46 @@ const BarcodeListComponent = ({ }, { key: 'manufacturer', label: 'label.manufacturer' }, { key: 'id', label: 'label.gtin' }, - 'selection', - ]); + ]; + + const columns = useColumns( + isAdmin ? [...columnDefs, 'selection'] : columnDefs + ); return ( <> - {isOpen && ( - - )} + {isAdmin && ( + <> + {isOpen && ( + + )} - - onOpen()} - isLoading={false} - startIcon={} - > - {t('label.add-barcode')} - - + + onOpen()} + isLoading={false} + startIcon={} + > + {t('label.add-barcode')} + + - - - { - await deleteGS1({ gtin: item.gtin }); - }} - /> - - + + + { + await deleteGS1({ gtin: item.gtin }); + }} + /> + + + + )} Date: Thu, 1 Feb 2024 17:36:03 +1300 Subject: [PATCH 31/38] move tooltip inside button --- .../buttons/standard/ButtonWithIcon.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/frontend/common/src/ui/components/buttons/standard/ButtonWithIcon.tsx b/frontend/common/src/ui/components/buttons/standard/ButtonWithIcon.tsx index 992678a11..3809032c5 100644 --- a/frontend/common/src/ui/components/buttons/standard/ButtonWithIcon.tsx +++ b/frontend/common/src/ui/components/buttons/standard/ButtonWithIcon.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ButtonProps, Tooltip } from '@mui/material'; +import { Box, ButtonProps, Tooltip } from '@mui/material'; import { ShrinkableBaseButton } from './ShrinkableBaseButton'; import { useIsScreen } from '@common/hooks'; @@ -41,23 +41,25 @@ export const ButtonWithIcon: React.FC = ({ const text = shrink ? null : label; return ( - - - - {centeredIcon} - {text} - - - + + + + + {centeredIcon} + {text} + + + + ); }; From bfc04a23324fc680d87c7d761b122fa93c6af09b Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 09:51:45 +1300 Subject: [PATCH 32/38] fix tetanus vaccine code --- data-loader/data/v2/vaccines.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-loader/data/v2/vaccines.csv b/data-loader/data/v2/vaccines.csv index a796ae705..1e88e0f18 100644 --- a/data-loader/data/v2/vaccines.csv +++ b/data-loader/data/v2/vaccines.csv @@ -115,7 +115,7 @@ Rubella Vaccine,620294bf,,Intramuscular,e97a1dad5,Injection (powder for),d3de859 Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU],cae7facb0,0.5 ml,9c36b4ae1,Vial,08b593d55,,,798306,19.3,19.3,,,51201621, Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU] per dose,36ccad6c3,5 ml,7849729ca,Vial,ca986d082,10 doses,f5425bfae,798306,19.3,19.3,,,51201621, Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU] per dose,36ccad6c3,10 ml,63e117987,Vial,930a43c27,20 doses,7840cc75b,798306,19.3,19.3,,,51201621, -Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU],10Lf [40IU],0.5 ml,9c36b4ae1,Pre-filled syringe,d28bc1e14,,,798306,19.3,19.3,,,51201621, +Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU],c7929cc2b,0.5 ml,9c36b4ae1,Pre-filled syringe,d28bc1e14,,,798306,19.3,19.3,,,51201621, Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,5Lf [40IU],317ff5999,0.5 ml,d2f6b7e2b,Ampoule,3e69e2639,,,798306,19.3,19.3,,,51201621, Tick-Borne Encephalitis Vaccine,659f44bf,TBE,Intramuscular,85c28fe9c,Injection (suspension),66ac7576c,,,Tick borne encephalitis virus,aa54d178a,TicoVac,a48f17629,2.4mcg,13a911bbd,0.5 ml,612a4d775,Pre-filled syringe,13e3a4275,,,,19.3,19.3,44741001000116109,44741071000116103,, Tick-Borne Encephalitis Vaccine,659f44bf,TBE,Intramuscular,85c28fe9c,Injection (suspension),66ac7576c,,,Tick borne encephalitis virus,aa54d178a,TicoVac Junior,f7245545f,2.4mcg,515cbe2ff,0.25 ml,d1ac1d094,Pre-filled syringe,5b4836ddc,,,,19.3,19.3,44741001000116109,44746261000116106,, From 88cf1a629e127c6adcd3421278be013f8c8f58d9 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 10:04:10 +1300 Subject: [PATCH 33/38] fix duplicate code --- data-loader/data/v2/vaccines.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-loader/data/v2/vaccines.csv b/data-loader/data/v2/vaccines.csv index 1e88e0f18..4e03f3167 100644 --- a/data-loader/data/v2/vaccines.csv +++ b/data-loader/data/v2/vaccines.csv @@ -110,7 +110,7 @@ Rotavirus Vaccine,61c5e4bf,,Oral,60235e8bc,Suspension,ba32ec9c3,,,G1P(8) strain, Rotavirus Vaccine,61c5e4bf,,Oral,60235e8bc,Suspension,ba32ec9c3,,,"G1, G2, G3, G4, P1A(8) strain",fe815b974,RotaTeq,149e3694b,2x10^6 infective units x 5 strains,ea0226cc3,2 ml,45362561d,,,,,1007640,19.3,19.3,29448361000116107,29448421000116107,51201618, Rubella Vaccine,620294bf,,Intramuscular,e97a1dad5,Injection (powder for),d3de859a8,,,Wistar RA 27/3 strain ,0eae15d25,Undefined,4f1de8891,1000 CCID50,21f583313,0.5 ml,728a24a24,Vial with diluent,d7934edda,,,762817,19.3,19.3,,,51201619, Rubella Vaccine,620294bf,,Intramuscular,e97a1dad5,Injection (powder for),d3de859a8,,,Wistar RA 27/3 strain ,0eae15d25,Undefined,4f1de8891,1000 CCID50 per dose,9e32ea3ad,1 ml,4bda94cc0,Vial with diluent,e762683de,2 doses,1443cfcc5,762817,19.3,19.3,,,51201619, -Rubella Vaccine ,620294bf,,Intramuscular,e97a1dad5,Injection (powder for),d3de859a8,,,Wistar RA 27/3 strain,0eae15d25,Undefined,4f1de8891,1000 CCID50 per dose,9e32ea3ad,2.5 ml,d715dc79e,Vial with diluent,4bda94cc0,5 doses,9fb7828ed,762817,19.3,19.3,,,51201619, +Rubella Vaccine ,620294bf,,Intramuscular,e97a1dad5,Injection (powder for),d3de859a8,,,Wistar RA 27/3 strain,0eae15d25,Undefined,4f1de8891,1000 CCID50 per dose,9e32ea3ad,2.5 ml,d715dc79e,Vial with diluent,d7a2cb181,5 doses,9fb7828ed,762817,19.3,19.3,,,51201619, Rubella Vaccine,620294bf,,Intramuscular,e97a1dad5,Injection (powder for),d3de859a8,,,Wistar RA 27/3 strain,0eae15d25,Undefined,4f1de8891,1000 CCID50 per dose,9e32ea3ad,5 ml,4771548f5,Vial with diluent,23a7376db,10 doses,3c33c29a8,762817,19.3,19.3,,,51201619, Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU],cae7facb0,0.5 ml,9c36b4ae1,Vial,08b593d55,,,798306,19.3,19.3,,,51201621, Tetanus Toxoid Vaccine,64ed34bf,TT,Intramuscular,9b9691784,Injection (suspension),e65f8bc88,,,Undefined,fb4a8f96f,Undefined,b1720763f,10Lf [40IU] per dose,36ccad6c3,5 ml,7849729ca,Vial,ca986d082,10 doses,f5425bfae,798306,19.3,19.3,,,51201621, From 00a0c460bc7039c170c336465626d345e291693b Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 10:06:47 +1300 Subject: [PATCH 34/38] check for invalid codes in data parsers --- data-loader/src/v2/ConsumableDataParser.ts | 8 ++++++++ data-loader/src/v2/DrugDataParser.ts | 8 ++++++++ data-loader/src/v2/VaccineDataParser.ts | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/data-loader/src/v2/ConsumableDataParser.ts b/data-loader/src/v2/ConsumableDataParser.ts index 0d3bcdc46..e6074ac5d 100644 --- a/data-loader/src/v2/ConsumableDataParser.ts +++ b/data-loader/src/v2/ConsumableDataParser.ts @@ -89,6 +89,14 @@ export class ConsumableDataParser { productDefinition.forEach(item => { if (!item.code) return; + const regex = /^[A-Za-z0-9]+$/; + if (!regex.test(item.code)) { + console.log( + `WARNING: Code ${item.code} contains non-alphanumeric characters! Skipping...` + ); + return; + } + if (item.code in graph) { // check for conflicts. if (item.type && graph[item.code].type != item.type) { diff --git a/data-loader/src/v2/DrugDataParser.ts b/data-loader/src/v2/DrugDataParser.ts index 84ae24ded..126a94f2f 100644 --- a/data-loader/src/v2/DrugDataParser.ts +++ b/data-loader/src/v2/DrugDataParser.ts @@ -105,6 +105,14 @@ export class DrugDataParser { productDefinition.forEach(item => { if (!item.code) return; + const regex = /^[A-Za-z0-9]+$/; + if (!regex.test(item.code)) { + console.log( + `WARNING: Code ${item.code} contains non-alphanumeric characters! Skipping...` + ); + return; + } + if (item.code in graph) { // check for conflicts. if (item.type && graph[item.code].type != item.type) { diff --git a/data-loader/src/v2/VaccineDataParser.ts b/data-loader/src/v2/VaccineDataParser.ts index 3e2d48045..a3f261a89 100644 --- a/data-loader/src/v2/VaccineDataParser.ts +++ b/data-loader/src/v2/VaccineDataParser.ts @@ -113,6 +113,14 @@ export class VaccineDataParser { productDefinition.forEach(item => { if (!item.code) return; + const regex = /^[A-Za-z0-9]+$/; + if (!regex.test(item.code)) { + console.log( + `WARNING: Code ${item.code} contains non-alphanumeric characters! Skipping...` + ); + return; + } + if (item.code in graph) { // check for conflicts. if (item.type && graph[item.code].type != item.type) { From df8a34db49193b3f931935ba57acd6268e084635 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 10:16:10 +1300 Subject: [PATCH 35/38] use dgraph aggregate to determine total barcode count --- backend/dgraph/src/barcode/barcodes.rs | 3 +++ backend/dgraph/src/barcode/mod.rs | 4 +++- backend/service/src/barcodes/mod.rs | 3 +-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/dgraph/src/barcode/barcodes.rs b/backend/dgraph/src/barcode/barcodes.rs index e8f50e13a..353f0a962 100644 --- a/backend/dgraph/src/barcode/barcodes.rs +++ b/backend/dgraph/src/barcode/barcodes.rs @@ -24,6 +24,9 @@ query barcodes($first: Int, $offset: Int) { code } } + aggregates: aggregateBarcode { + count + } } "#; let data = client diff --git a/backend/dgraph/src/barcode/mod.rs b/backend/dgraph/src/barcode/mod.rs index 729f2661d..366af9737 100644 --- a/backend/dgraph/src/barcode/mod.rs +++ b/backend/dgraph/src/barcode/mod.rs @@ -1,6 +1,6 @@ use serde::Deserialize; -use crate::Entity; +use crate::{AggregateResult, Entity}; pub mod barcode; pub mod barcodes; @@ -17,4 +17,6 @@ pub struct Barcode { #[derive(Deserialize, Debug, Clone)] pub struct BarcodeData { pub data: Vec, + #[serde(default)] + pub aggregates: Option, } diff --git a/backend/service/src/barcodes/mod.rs b/backend/service/src/barcodes/mod.rs index 1259fcdd1..b4d05bb81 100644 --- a/backend/service/src/barcodes/mod.rs +++ b/backend/service/src/barcodes/mod.rs @@ -9,7 +9,6 @@ use dgraph::{ Barcode, DgraphClient, GraphQLError, }; use repository::RepositoryError; -use util::usize_to_u32; use crate::{service_provider::ServiceProvider, settings::Settings}; @@ -92,8 +91,8 @@ impl BarcodeService { match result { Some(data) => Ok(BarcodeCollection { - total_length: usize_to_u32(data.data.len()), data: data.data, + total_length: data.aggregates.unwrap_or_default().count, }), None => Ok(BarcodeCollection { data: vec![], From 428c3cae2ae490823d967b05f69e86836b5a2405 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 10:20:30 +1300 Subject: [PATCH 36/38] internal error -> bad user input --- backend/graphql/barcode/src/mutations/delete.rs | 1 + backend/graphql/barcode/src/mutations/mod.rs | 1 + backend/service/src/barcodes/mod.rs | 1 + backend/service/src/barcodes/upsert.rs | 6 +++--- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/graphql/barcode/src/mutations/delete.rs b/backend/graphql/barcode/src/mutations/delete.rs index 9fc406671..6495de62c 100644 --- a/backend/graphql/barcode/src/mutations/delete.rs +++ b/backend/graphql/barcode/src/mutations/delete.rs @@ -42,6 +42,7 @@ fn map_modify_barcode_error(error: ModifyBarcodeError) -> Result { ModifyBarcodeError::BarcodeAlreadyExists => BadUserInput(formatted_error), ModifyBarcodeError::BarcodeDoesNotExist => BadUserInput(formatted_error), ModifyBarcodeError::UniversalCodeDoesNotExist => BadUserInput(formatted_error), + ModifyBarcodeError::BadUserInput(message) => BadUserInput(message), ModifyBarcodeError::InternalError(message) => InternalError(message), ModifyBarcodeError::DatabaseError(_) => InternalError(formatted_error), ModifyBarcodeError::DgraphError(gql_error) => { diff --git a/backend/graphql/barcode/src/mutations/mod.rs b/backend/graphql/barcode/src/mutations/mod.rs index 15ae286bc..1156d6bac 100644 --- a/backend/graphql/barcode/src/mutations/mod.rs +++ b/backend/graphql/barcode/src/mutations/mod.rs @@ -17,6 +17,7 @@ pub fn map_modify_barcode_error(error: ModifyBarcodeError) -> Result BadUserInput(formatted_error), ModifyBarcodeError::BarcodeDoesNotExist => BadUserInput(formatted_error), ModifyBarcodeError::UniversalCodeDoesNotExist => BadUserInput(formatted_error), + ModifyBarcodeError::BadUserInput(message) => BadUserInput(message), ModifyBarcodeError::InternalError(message) => InternalError(message), ModifyBarcodeError::DatabaseError(_) => InternalError(formatted_error), ModifyBarcodeError::DgraphError(gql_error) => { diff --git a/backend/service/src/barcodes/mod.rs b/backend/service/src/barcodes/mod.rs index b4d05bb81..8c64f3b27 100644 --- a/backend/service/src/barcodes/mod.rs +++ b/backend/service/src/barcodes/mod.rs @@ -17,6 +17,7 @@ pub enum ModifyBarcodeError { BarcodeDoesNotExist, BarcodeAlreadyExists, UniversalCodeDoesNotExist, + BadUserInput(String), InternalError(String), DatabaseError(RepositoryError), DgraphError(GraphQLError), diff --git a/backend/service/src/barcodes/upsert.rs b/backend/service/src/barcodes/upsert.rs index 15ade851b..b27c59eff 100644 --- a/backend/service/src/barcodes/upsert.rs +++ b/backend/service/src/barcodes/upsert.rs @@ -82,19 +82,19 @@ pub async fn validate( new_barcode: &AddBarcode, ) -> Result<(), ModifyBarcodeError> { if new_barcode.gtin.is_empty() { - return Err(ModifyBarcodeError::InternalError( + return Err(ModifyBarcodeError::BadUserInput( "GTIN is required".to_string(), )); } if new_barcode.manufacturer.is_empty() { - return Err(ModifyBarcodeError::InternalError( + return Err(ModifyBarcodeError::BadUserInput( "Manufacturer is required".to_string(), )); } if new_barcode.entity_code.is_empty() { - return Err(ModifyBarcodeError::InternalError( + return Err(ModifyBarcodeError::BadUserInput( "Entity code is required".to_string(), )); } From 58f5ad14fd9deca6fa14dbecbd48febb2e172c3d Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 10:24:16 +1300 Subject: [PATCH 37/38] revert "fix the detail redirect" --- frontend/host/src/Site.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/host/src/Site.tsx b/frontend/host/src/Site.tsx index c120452a7..c7dc3b2a8 100644 --- a/frontend/host/src/Site.tsx +++ b/frontend/host/src/Site.tsx @@ -92,7 +92,7 @@ export const Site: FC = () => { /> } /> - } /> + } /> } /> From c338300546172ed92df56823b8d50feddf266560 Mon Sep 17 00:00:00 2001 From: Lache Melvin Date: Fri, 2 Feb 2024 10:24:46 +1300 Subject: [PATCH 38/38] fix the detail redirect --- frontend/host/src/Site.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/host/src/Site.tsx b/frontend/host/src/Site.tsx index c7dc3b2a8..c120452a7 100644 --- a/frontend/host/src/Site.tsx +++ b/frontend/host/src/Site.tsx @@ -92,7 +92,7 @@ export const Site: FC = () => { /> } /> - } /> + } /> } />