Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5390 correctly handle gs1 2d data matrix format #5401

Open
wants to merge 3 commits into
base: v2.4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export const AddFromScannerButtonComponent = () => {
const equipmentRoute = RouteBuilder.create(AppRoute.Coldchain).addPart(
AppRoute.Equipment
);
const { mutateAsync: scanAsset } = useAssets.document.scan();
const { mutateAsync: fetchAsset } = useAssets.document.fetch();
const { mutateAsync: fetchOrCreateFromGS1 } = useAssets.document.gs1();
const { mutateAsync: saveNewAsset } = useAssets.document.insert();
const { insertLog, invalidateQueries } = useAssets.log.insert();
const newAssetData = useRef<DraftAsset>();
Expand Down Expand Up @@ -64,9 +65,23 @@ export const AddFromScannerButtonComponent = () => {

const handleScanResult = async (result: ScanResult) => {
if (!!result.content) {
const { content } = result;
const { gs1 } = result;

const asset = await scanAsset(content).catch(() => {});
if (!gs1) {
// try to fetch the asset by id, as it could be an id from our own barcode
jmbrunskill marked this conversation as resolved.
Show resolved Hide resolved
const { content: id } = result;
const asset = await fetchAsset(id).catch(() => {});
if (asset) {
navigate(equipmentRoute.addPart(id).build());

return;
}
error(t('error.no-matching-asset', { id }))();
return;
}

// send the GS1 data to backend to handle
const asset = await fetchOrCreateFromGS1(gs1).catch(() => {});

if (asset?.__typename !== 'AssetNode') {
error(t('error.no-matching-asset', { id: result.content }))();
Expand Down
13 changes: 9 additions & 4 deletions client/packages/coldchain/src/Equipment/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import {
setNullableInput,
InsertAssetLogInput,
AssetLogSortFieldInput,
Gs1DataElement,
} from '@openmsupply-client/common';
import { Sdk, AssetFragment } from './operations.generated';
import { CCE_CLASS_ID } from '../utils';
import { DraftAsset } from '../types';
import { Gs1Barcode } from 'gs1-barcode-parser-mod';

export type ListParams<T> = {
first: number;
Expand Down Expand Up @@ -97,12 +99,15 @@ export const getAssetQueries = (sdk: Sdk, storeId: string) => ({

throw new Error('Asset not found');
},
byScannerString: async (inputText: string) => {
const { assetByScannedString } = await sdk.assetByScannedString({
byGs1Elements: async (data: Gs1Barcode) => {
let dataElements: Gs1DataElement[] = data.parsedCodeItems.map(item => {
return { ai: item.ai, data: item.data.toString() };
});
const { assetFromGs1Data } = await sdk.assetFromGs1Data({
storeId,
inputText,
data: dataElements,
});
return assetByScannedString;
return assetFromGs1Data;
},
list: async (
{ first, offset, sortBy, filterBy }: ListParams<AssetFragment>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
useAsset,
useFetchAssetById,
useFetchAssetByScannerString,
} from './useAsset';
import { useAsset, useFetchAssetById, useFetchAssetByGS1 } from './useAsset';
import { useAssetDelete } from './useAssetDelete';
import { useAssetFields } from './useAssetFields';
import { useAssetInsert } from './useAssetInsert';
Expand All @@ -22,6 +18,6 @@ export const Document = {
useAssetsDelete,
useAssetUpdate,
useFetchAssetById,
useFetchAssetByScannerString,
useFetchAssetByGS1,
useAssetProperties,
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export const useFetchAssetById = () => {
});
};

export const useFetchAssetByScannerString = () => {
export const useFetchAssetByGS1 = () => {
const api = useAssetApi();
return useMutation(api.get.byScannerString, {
return useMutation(api.get.byGs1Elements, {
onError: () => {},
});
};
2 changes: 1 addition & 1 deletion client/packages/coldchain/src/Equipment/api/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const useAssets = {

document: {
fetch: Document.useFetchAssetById,
scan: Document.useFetchAssetByScannerString,
gs1: Document.useFetchAssetByGS1,
get: Document.useAsset,
list: Document.useAssets,
listAll: Document.useAssetsAll,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export type AssetByIdQueryVariables = Types.Exact<{

export type AssetByIdQuery = { __typename: 'Queries', assets: { __typename: 'AssetConnector', totalCount: number, nodes: Array<{ __typename: 'AssetNode', catalogueItemId?: string | null, assetNumber?: string | null, createdDatetime: any, id: string, installationDate?: string | null, properties: string, catalogProperties?: string | null, modifiedDatetime: any, notes?: string | null, replacementDate?: string | null, serialNumber?: string | null, storeId?: string | null, donorNameId?: string | null, warrantyStart?: string | null, warrantyEnd?: string | null, needsReplacement?: boolean | null, documents: { __typename: 'SyncFileReferenceConnector', nodes: Array<{ __typename: 'SyncFileReferenceNode', fileName: string, id: string, mimeType?: string | null }> }, locations: { __typename: 'LocationConnector', totalCount: number, nodes: Array<{ __typename: 'LocationNode', id: string, code: string, name: string, onHold: boolean, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, maxTemperature: number, minTemperature: number } | null }> }, statusLog?: { __typename: 'AssetLogNode', logDatetime: any, status?: Types.StatusType | null, reason?: { __typename: 'AssetLogReasonNode', reason: string } | null } | null, store?: { __typename: 'StoreNode', id: string, code: string, storeName: string } | null, catalogueItem?: { __typename: 'AssetCatalogueItemNode', manufacturer?: string | null, model: string } | null, assetType?: { __typename: 'AssetTypeNode', id: string, name: string } | null, assetClass?: { __typename: 'AssetClassNode', id: string, name: string } | null, assetCategory?: { __typename: 'AssetCategoryNode', id: string, name: string } | null, donor?: { __typename: 'NameNode', id: string, name: string } | null }> } };

export type AssetByScannedStringQueryVariables = Types.Exact<{
export type AssetFromGs1DataQueryVariables = Types.Exact<{
storeId: Types.Scalars['String']['input'];
inputText: Types.Scalars['String']['input'];
data: Array<Types.Gs1DataElement> | Types.Gs1DataElement;
}>;


export type AssetByScannedStringQuery = { __typename: 'Queries', assetByScannedString: { __typename: 'AssetNode', catalogueItemId?: string | null, assetNumber?: string | null, createdDatetime: any, id: string, installationDate?: string | null, properties: string, catalogProperties?: string | null, modifiedDatetime: any, notes?: string | null, replacementDate?: string | null, serialNumber?: string | null, storeId?: string | null, donorNameId?: string | null, warrantyStart?: string | null, warrantyEnd?: string | null, needsReplacement?: boolean | null, documents: { __typename: 'SyncFileReferenceConnector', nodes: Array<{ __typename: 'SyncFileReferenceNode', fileName: string, id: string, mimeType?: string | null }> }, locations: { __typename: 'LocationConnector', totalCount: number, nodes: Array<{ __typename: 'LocationNode', id: string, code: string, name: string, onHold: boolean, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, maxTemperature: number, minTemperature: number } | null }> }, statusLog?: { __typename: 'AssetLogNode', logDatetime: any, status?: Types.StatusType | null, reason?: { __typename: 'AssetLogReasonNode', reason: string } | null } | null, store?: { __typename: 'StoreNode', id: string, code: string, storeName: string } | null, catalogueItem?: { __typename: 'AssetCatalogueItemNode', manufacturer?: string | null, model: string } | null, assetType?: { __typename: 'AssetTypeNode', id: string, name: string } | null, assetClass?: { __typename: 'AssetClassNode', id: string, name: string } | null, assetCategory?: { __typename: 'AssetCategoryNode', id: string, name: string } | null, donor?: { __typename: 'NameNode', id: string, name: string } | null } | { __typename: 'ScannedDataParseError' } };
export type AssetFromGs1DataQuery = { __typename: 'Queries', assetFromGs1Data: { __typename: 'AssetNode', catalogueItemId?: string | null, assetNumber?: string | null, createdDatetime: any, id: string, installationDate?: string | null, properties: string, catalogProperties?: string | null, modifiedDatetime: any, notes?: string | null, replacementDate?: string | null, serialNumber?: string | null, storeId?: string | null, donorNameId?: string | null, warrantyStart?: string | null, warrantyEnd?: string | null, needsReplacement?: boolean | null, documents: { __typename: 'SyncFileReferenceConnector', nodes: Array<{ __typename: 'SyncFileReferenceNode', fileName: string, id: string, mimeType?: string | null }> }, locations: { __typename: 'LocationConnector', totalCount: number, nodes: Array<{ __typename: 'LocationNode', id: string, code: string, name: string, onHold: boolean, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, maxTemperature: number, minTemperature: number } | null }> }, statusLog?: { __typename: 'AssetLogNode', logDatetime: any, status?: Types.StatusType | null, reason?: { __typename: 'AssetLogReasonNode', reason: string } | null } | null, store?: { __typename: 'StoreNode', id: string, code: string, storeName: string } | null, catalogueItem?: { __typename: 'AssetCatalogueItemNode', manufacturer?: string | null, model: string } | null, assetType?: { __typename: 'AssetTypeNode', id: string, name: string } | null, assetClass?: { __typename: 'AssetClassNode', id: string, name: string } | null, assetCategory?: { __typename: 'AssetCategoryNode', id: string, name: string } | null, donor?: { __typename: 'NameNode', id: string, name: string } | null } | { __typename: 'ScannedDataParseError' } };

export type AssetLogsQueryVariables = Types.Exact<{
filter: Types.AssetLogFilterInput;
Expand Down Expand Up @@ -250,9 +250,9 @@ export const AssetByIdDocument = gql`
}
}
${AssetFragmentDoc}`;
export const AssetByScannedStringDocument = gql`
query assetByScannedString($storeId: String!, $inputText: String!) {
assetByScannedString(storeId: $storeId, inputText: $inputText) {
export const AssetFromGs1DataDocument = gql`
query assetFromGs1Data($storeId: String!, $data: [Gs1DataElement!]!) {
assetFromGs1Data(storeId: $storeId, gs1: $data) {
... on AssetNode {
...Asset
}
Expand Down Expand Up @@ -363,8 +363,8 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
assetById(variables: AssetByIdQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AssetByIdQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<AssetByIdQuery>(AssetByIdDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'assetById', 'query', variables);
},
assetByScannedString(variables: AssetByScannedStringQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AssetByScannedStringQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<AssetByScannedStringQuery>(AssetByScannedStringDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'assetByScannedString', 'query', variables);
assetFromGs1Data(variables: AssetFromGs1DataQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AssetFromGs1DataQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<AssetFromGs1DataQuery>(AssetFromGs1DataDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'assetFromGs1Data', 'query', variables);
},
assetLogs(variables: AssetLogsQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AssetLogsQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<AssetLogsQuery>(AssetLogsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'assetLogs', 'query', variables);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ query assetById($storeId: String!, $assetId: String!) {
}
}

query assetByScannedString($storeId: String!, $inputText: String!) {
assetByScannedString(storeId: $storeId, inputText: $inputText) {
query assetFromGs1Data($storeId: String!, $data: [Gs1DataElement!]!) {
assetFromGs1Data(storeId: $storeId, gs1: $data) {
... on AssetNode {
...Asset
}
Expand Down
19 changes: 12 additions & 7 deletions client/packages/common/src/types/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2344,6 +2344,11 @@ export type GeneratedCustomerReturnLineConnector = {
totalCount: Scalars['Int']['output'];
};

export type Gs1DataElement = {
ai: Scalars['String']['input'];
data: Scalars['String']['input'];
};

export type InboundInvoiceCounts = {
__typename: 'InboundInvoiceCounts';
created: InvoiceCountsSummary;
Expand Down Expand Up @@ -5457,13 +5462,13 @@ export type Queries = {
activeProgramEvents: ProgramEventResponse;
activityLogs: ActivityLogResponse;
apiVersion: Scalars['String']['output'];
assetByScannedString: AssetParseResponse;
assetCatalogueItem: AssetCatalogueItemResponse;
assetCatalogueItems: AssetCatalogueItemsResponse;
assetCategories: AssetCategoriesResponse;
assetCategory: AssetCategoryResponse;
assetClass: AssetClassResponse;
assetClasses: AssetClassesResponse;
assetFromGs1Data: AssetParseResponse;
assetLogReasons: AssetLogReasonsResponse;
assetLogs: AssetLogsResponse;
assetProperties: AssetPropertiesResponse;
Expand Down Expand Up @@ -5630,12 +5635,6 @@ export type QueriesActivityLogsArgs = {
};


export type QueriesAssetByScannedStringArgs = {
inputText: Scalars['String']['input'];
storeId: Scalars['String']['input'];
};


export type QueriesAssetCatalogueItemArgs = {
id: Scalars['String']['input'];
};
Expand Down Expand Up @@ -5672,6 +5671,12 @@ export type QueriesAssetClassesArgs = {
};


export type QueriesAssetFromGs1DataArgs = {
gs1: Array<Gs1DataElement>;
storeId: Scalars['String']['input'];
};


export type QueriesAssetLogReasonsArgs = {
filter?: InputMaybe<AssetLogReasonFilterInput>;
page?: InputMaybe<PaginationInput>;
Expand Down
4 changes: 3 additions & 1 deletion client/packages/common/src/utils/BarcodeScannerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { Capacitor } from '@capacitor/core';
import { GlobalStyles } from '@mui/material';
import { useNotification } from '../hooks/useNotification';
import { useTranslation } from '@common/intl';
import { parseBarcode } from 'gs1-barcode-parser-mod';
import { Gs1Barcode, parseBarcode } from 'gs1-barcode-parser-mod';
import { Formatter } from './formatters';
import { BarcodeScanner, ScannerType } from '@openmsupply-client/common';

const SCAN_TIMEOUT_IN_MS = 5000;

export interface ScanResult {
gs1?: Gs1Barcode;
batch?: string;
content?: string;
expiryDate?: string | null;
Expand Down Expand Up @@ -70,6 +71,7 @@ export const parseResult = (content?: string): ScanResult => {

return {
batch,
gs1,
content,
expiryDate: expiry ? Formatter.naiveDate(expiry) : undefined,
gtin,
Expand Down
13 changes: 7 additions & 6 deletions server/graphql/asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use service::auth::{Resource, ResourceAccessRequest};

use types::{
map_parse_error, AssetConnector, AssetFilterInput, AssetNode, AssetParseResponse,
AssetSortInput, AssetsResponse, ScannedDataParseError,
AssetSortInput, AssetsResponse, GS1DataElement, ScannedDataParseError,
};

#[derive(Default, Clone)]
Expand Down Expand Up @@ -61,11 +61,11 @@ impl AssetQueries {
)))
}

pub async fn asset_by_scanned_string(
pub async fn asset_from_gs1_data(
&self,
ctx: &Context<'_>,
store_id: String,
input_text: String,
gs1: Vec<GS1DataElement>,
) -> Result<AssetParseResponse> {
let user = validate_auth(
ctx,
Expand All @@ -78,9 +78,10 @@ impl AssetQueries {
let service_provider = ctx.service_provider();
let service_context = service_provider.context(store_id.clone(), user.user_id)?;

let result = service_provider
.asset_service
.parse_scanned_data(&service_context, input_text);
let result = service_provider.asset_service.asset_from_gs1_data(
&service_context,
gs1.into_iter().map(GS1DataElement::to_domain).collect(),
);

match result {
Ok(asset) => Ok(AssetParseResponse::Response(AssetNode::from_domain(asset))),
Expand Down
2 changes: 1 addition & 1 deletion server/graphql/asset/src/types/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use repository::{
EqualFilter,
};
use repository::{DateFilter, StringFilter};
use service::asset::parse::ScannedDataParseError as ServiceScannedDataParseError;
use service::asset::parse::AssetFromGs1Error as ServiceScannedDataParseError;
use service::{usize_to_u32, ListResult};

use super::{AssetLogNode, AssetLogStatusInput, EqualFilterStatusInput};
Expand Down
17 changes: 17 additions & 0 deletions server/graphql/asset/src/types/gs1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use async_graphql::*;
use util::GS1DataElement as DomainGS1DataElement;

#[derive(InputObject, Clone)]
pub struct GS1DataElement {
ai: String,
data: String,
}

impl GS1DataElement {
pub fn to_domain(self) -> DomainGS1DataElement {
DomainGS1DataElement {
ai: self.ai.clone(),
data: self.data.clone(),
}
}
}
2 changes: 2 additions & 0 deletions server/graphql/asset/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ pub mod asset;
pub use asset::*;
pub mod asset_property;
pub use asset_property::*;
pub mod gs1;
pub use gs1::*;
11 changes: 6 additions & 5 deletions server/service/src/asset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ use self::update::{update_asset, UpdateAsset, UpdateAssetError};

use super::{ListError, ListResult};
use crate::{service_provider::ServiceContext, SingleRecordError};
use parse::ScannedDataParseError;
use parse::AssetFromGs1Error;
use repository::asset_log_reason::{AssetLogReason, AssetLogReasonFilter, AssetLogReasonSort};
use repository::asset_property::AssetPropertyFilter;
use repository::asset_property_row::AssetPropertyRow;
use repository::assets::asset::{Asset, AssetFilter, AssetSort};
use repository::assets::asset_log::{AssetLog, AssetLogFilter, AssetLogSort};
use repository::{PaginationOption, StorageConnection};
use util::GS1DataElement;

pub mod delete;
pub mod delete_log_reason;
Expand Down Expand Up @@ -139,12 +140,12 @@ pub trait AssetServiceTrait: Sync + Send {
get_asset_properties(connection, filter)
}

fn parse_scanned_data(
fn asset_from_gs1_data(
&self,
ctx: &ServiceContext,
scanned_data: String,
) -> Result<Asset, ScannedDataParseError> {
parse::parse_from_scanned_data(ctx, scanned_data)
gs1_data: Vec<GS1DataElement>,
) -> Result<Asset, AssetFromGs1Error> {
parse::get_or_create_from_gs1_data(ctx, gs1_data)
}
}

Expand Down
Loading
Loading