Skip to content

Commit ebb5618

Browse files
authored
Merge pull request #6754 from msupply-foundation/6731-add-message-new-stock-reason
6731 Handle error when no reason provided for adding/adjusting stock
2 parents 22fee41 + 26584b5 commit ebb5618

File tree

13 files changed

+148
-57
lines changed

13 files changed

+148
-57
lines changed

client/packages/common/src/intl/locales/en/common.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@
349349
"error.problem-saving": "An error occurred: there was a problem saving the data.",
350350
"error.program-already-exists": "Immunization program already exists",
351351
"error.provide-reason": "A reason must be provided for any rows which have a difference in the counted number of packs.",
352+
"error.provide-reason-stock-adjustment": "A reason must be provided before adjusting stock",
353+
"error.provide-reason-new-stock": "A reason must be provided before adding stock",
352354
"error.provide-valid-reason": "Adjustment reason does not match adjustment direction",
353355
"error.reasons-not-provided-program-requisition": "Reasons must be provided when suggested quantity differs from requested quantity.",
354356
"error.record-already-exists": "A record with this id already exists",
@@ -1900,4 +1902,4 @@
19001902
"warning.caps-lock": "Warning: Caps lock is on",
19011903
"warning.field-not-parsed": "{{field}} not parsed",
19021904
"warning.nothing-to-supply": "Nothing left to supply!"
1903-
}
1905+
}

client/packages/common/src/types/schema.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export type AddToShipmentFromMasterListInput = {
219219
shipmentId: Scalars['String']['input'];
220220
};
221221

222-
export type AdjustmentReasonNotProvided = InsertStocktakeLineErrorInterface & UpdateStocktakeLineErrorInterface & {
222+
export type AdjustmentReasonNotProvided = InsertInventoryAdjustmentErrorInterface & InsertStockLineErrorInterface & InsertStocktakeLineErrorInterface & UpdateStocktakeLineErrorInterface & {
223223
__typename: 'AdjustmentReasonNotProvided';
224224
description: Scalars['String']['output'];
225225
};
@@ -3220,6 +3220,15 @@ export type InsertRnRFormInput = {
32203220

32213221
export type InsertRnRFormResponse = RnRFormNode;
32223222

3223+
export type InsertStockLineError = {
3224+
__typename: 'InsertStockLineError';
3225+
error: InsertStockLineErrorInterface;
3226+
};
3227+
3228+
export type InsertStockLineErrorInterface = {
3229+
description: Scalars['String']['output'];
3230+
};
3231+
32233232
export type InsertStockLineInput = {
32243233
/** Empty barcode will unlink barcode from StockLine */
32253234
barcode?: InputMaybe<Scalars['String']['input']>;
@@ -3237,7 +3246,7 @@ export type InsertStockLineInput = {
32373246
sellPricePerPack: Scalars['Float']['input'];
32383247
};
32393248

3240-
export type InsertStockLineLineResponse = StockLineNode;
3249+
export type InsertStockLineLineResponse = InsertStockLineError | StockLineNode;
32413250

32423251
export type InsertStocktakeInput = {
32433252
comment?: InputMaybe<Scalars['String']['input']>;

client/packages/system/src/Stock/Components/InventoryAdjustment/InventoryAdjustmentModal.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export const InventoryAdjustmentModal: FC<InventoryAdjustmentModalProps> = ({
3131
const { draft, setDraft, create } = useInventoryAdjustment(stockLine);
3232

3333
const packUnit = String(stockLine.packSize);
34-
3534
const saveDisabled = draft.adjustment === 0;
3635

3736
const save = async () => {
@@ -48,7 +47,7 @@ export const InventoryAdjustmentModal: FC<InventoryAdjustmentModalProps> = ({
4847
const errorSnack = error(t(result));
4948
errorSnack();
5049
} catch {
51-
// TODO: handle error if no reason selected when reasons required
50+
error(t('messages.could-not-save'))(); // generic could not save message
5251
}
5352
};
5453

client/packages/system/src/Stock/Components/NewStockLineModal.tsx

+37-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
useNavigate,
1414
RouteBuilder,
1515
usePluginEvents,
16+
noOtherVariants,
1617
} from '@openmsupply-client/common';
1718
import { useStockLine } from '../api';
1819
import { StockLineForm } from './StockLineForm';
@@ -34,7 +35,7 @@ export const NewStockLineModal: FC<NewStockLineModalProps> = ({
3435
}) => {
3536
const t = useTranslation();
3637
const navigate = useNavigate();
37-
const { success } = useNotification();
38+
const { success, error } = useNotification();
3839
const pluginEvents = usePluginEvents({
3940
isDirty: false,
4041
});
@@ -51,20 +52,45 @@ export const NewStockLineModal: FC<NewStockLineModalProps> = ({
5152
const isDisabled =
5253
!draft.itemId || !draft.packSize || !draft.totalNumberOfPacks;
5354

55+
const mapStructuredErrors = (result: Awaited<ReturnType<typeof create>>) => {
56+
if (result.insertStockLine.__typename === 'StockLineNode') {
57+
return;
58+
}
59+
const { error } = result.insertStockLine;
60+
switch (error.__typename) {
61+
case 'AdjustmentReasonNotProvided':
62+
return t('error.provide-reason-new-stock');
63+
default:
64+
return noOtherVariants(error.__typename);
65+
}
66+
};
67+
5468
const save = async () => {
5569
try {
5670
const result = await create();
57-
const successSnack = success(t('messages.stock-line-saved'));
58-
successSnack();
59-
onClose();
60-
navigate(
61-
RouteBuilder.create(AppRoute.Inventory)
62-
.addPart(AppRoute.Stock)
63-
.addPart(result.insertStockLine.id)
64-
.build()
65-
);
71+
72+
if (result?.insertStockLine.__typename === 'InsertStockLineError') {
73+
const errorMessage = mapStructuredErrors(result);
74+
if (errorMessage) {
75+
error(errorMessage)();
76+
}
77+
}
78+
79+
if (result?.insertStockLine.__typename === 'StockLineNode') {
80+
const successSnack = success(t('messages.stock-line-saved'));
81+
successSnack();
82+
onClose();
83+
navigate(
84+
RouteBuilder.create(AppRoute.Inventory)
85+
.addPart(AppRoute.Stock)
86+
.addPart(result?.insertStockLine.id)
87+
.build()
88+
);
89+
}
90+
91+
updatePatch(draft);
6692
} catch {
67-
// todo
93+
error(t('messages.could-not-save'))(); // generic could not save message
6894
}
6995
};
7096

client/packages/system/src/Stock/api/hooks/useInventoryAdjustment.ts

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ export function useInventoryAdjustment(stockLine: StockLineRowFragment) {
3939
if (adjustmentError.__typename === 'StockLineReducedBelowZero') {
4040
return 'error.reduced-below-zero';
4141
}
42+
43+
if (adjustmentError.__typename === 'AdjustmentReasonNotProvided') {
44+
return 'error.provide-reason-stock-adjustment';
45+
}
4246
};
4347

4448
return {

client/packages/system/src/Stock/api/operations.generated.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ export type CreateInventoryAdjustmentMutationVariables = Types.Exact<{
8282
}>;
8383

8484

85-
export type CreateInventoryAdjustmentMutation = { __typename: 'Mutations', createInventoryAdjustment: { __typename: 'CreateInventoryAdjustmentError', error: { __typename: 'StockLineReducedBelowZero', description: string } } | { __typename: 'InvoiceNode', id: string, lines: { __typename: 'InvoiceLineConnector', nodes: Array<{ __typename: 'InvoiceLineNode', id: string, itemName: string, numberOfPacks: number, itemCode: string, stockLine?: { __typename: 'StockLineNode', id: string } | null }> } } };
85+
export type CreateInventoryAdjustmentMutation = { __typename: 'Mutations', createInventoryAdjustment: { __typename: 'CreateInventoryAdjustmentError', error: { __typename: 'AdjustmentReasonNotProvided', description: string } | { __typename: 'StockLineReducedBelowZero', description: string } } | { __typename: 'InvoiceNode', id: string, lines: { __typename: 'InvoiceLineConnector', nodes: Array<{ __typename: 'InvoiceLineNode', id: string, itemName: string, numberOfPacks: number, itemCode: string, stockLine?: { __typename: 'StockLineNode', id: string } | null }> } } };
8686

8787
export type InsertStockLineMutationVariables = Types.Exact<{
8888
input: Types.InsertStockLineInput;
8989
storeId: Types.Scalars['String']['input'];
9090
}>;
9191

9292

93-
export type InsertStockLineMutation = { __typename: 'Mutations', insertStockLine: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, locationId?: string | null, itemVariantId?: string | null, locationName?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, supplierName?: string | null, barcode?: string | null, location?: { __typename: 'LocationNode', id: string, name: string, onHold: boolean, code: string, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, maxTemperature: number, minTemperature: number } | null } | null, item: { __typename: 'ItemNode', code: string, name: string, unitName?: string | null, masterLists?: Array<{ __typename: 'MasterListNode', name: string }> | null } } };
93+
export type InsertStockLineMutation = { __typename: 'Mutations', insertStockLine: { __typename: 'InsertStockLineError', error: { __typename: 'AdjustmentReasonNotProvided' } } | { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, locationId?: string | null, itemVariantId?: string | null, locationName?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, supplierName?: string | null, barcode?: string | null, location?: { __typename: 'LocationNode', id: string, name: string, onHold: boolean, code: string, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, maxTemperature: number, minTemperature: number } | null } | null, item: { __typename: 'ItemNode', code: string, name: string, unitName?: string | null, masterLists?: Array<{ __typename: 'MasterListNode', name: string }> | null } } };
9494

9595
export const StockLineRowFragmentDoc = gql`
9696
fragment StockLineRow on StockLineNode {
@@ -289,11 +289,16 @@ export const CreateInventoryAdjustmentDocument = gql`
289289
... on CreateInventoryAdjustmentError {
290290
__typename
291291
error {
292+
__typename
292293
description
293294
... on StockLineReducedBelowZero {
294295
__typename
295296
description
296297
}
298+
... on AdjustmentReasonNotProvided {
299+
__typename
300+
description
301+
}
297302
}
298303
}
299304
}
@@ -306,6 +311,15 @@ export const InsertStockLineDocument = gql`
306311
__typename
307312
...StockLineRow
308313
}
314+
... on InsertStockLineError {
315+
__typename
316+
error {
317+
__typename
318+
... on AdjustmentReasonNotProvided {
319+
__typename
320+
}
321+
}
322+
}
309323
}
310324
}
311325
${StockLineRowFragmentDoc}`;

client/packages/system/src/Stock/api/operations.graphql

+14
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,16 @@ mutation createInventoryAdjustment(
202202
... on CreateInventoryAdjustmentError {
203203
__typename
204204
error {
205+
__typename
205206
description
206207
... on StockLineReducedBelowZero {
207208
__typename
208209
description
209210
}
211+
... on AdjustmentReasonNotProvided {
212+
__typename
213+
description
214+
}
210215
}
211216
}
212217
}
@@ -218,5 +223,14 @@ mutation insertStockLine($input: InsertStockLineInput!, $storeId: String!) {
218223
__typename
219224
...StockLineRow
220225
}
226+
... on InsertStockLineError {
227+
__typename
228+
error {
229+
__typename
230+
... on AdjustmentReasonNotProvided {
231+
__typename
232+
}
233+
}
234+
}
221235
}
222236
}

server/graphql/inventory_adjustment/mutations/insert.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use async_graphql::*;
22
use graphql_core::standard_graphql_error::StandardGraphqlError;
33
use graphql_core::{standard_graphql_error::validate_auth, ContextExt};
44
use graphql_types::generic_errors::StockLineReducedBelowZero;
5-
use graphql_types::types::InvoiceNode;
5+
use graphql_types::types::{AdjustmentReasonNotProvided, InvoiceNode};
66
use service::invoice::inventory_adjustment::InsertInventoryAdjustmentError as ServiceError;
77
use service::{
88
auth::{Resource, ResourceAccessRequest},
@@ -91,6 +91,7 @@ impl CreateInventoryAdjustmentInput {
9191
#[graphql(field(name = "description", ty = "String"))]
9292
pub enum InsertErrorInterface {
9393
StockLineReducedBelowZero(StockLineReducedBelowZero),
94+
AdustmentReasonNotProvided(AdjustmentReasonNotProvided),
9495
}
9596

9697
fn map_error(error: ServiceError) -> Result<InsertErrorInterface> {
@@ -104,12 +105,17 @@ fn map_error(error: ServiceError) -> Result<InsertErrorInterface> {
104105
))
105106
}
106107

108+
ServiceError::AdjustmentReasonNotProvided => {
109+
return Ok(InsertErrorInterface::AdustmentReasonNotProvided(
110+
AdjustmentReasonNotProvided,
111+
))
112+
}
113+
107114
// Standard Graphql Errors
108115
ServiceError::StockLineDoesNotExist
109116
| ServiceError::InvalidStore
110117
| ServiceError::InvalidAdjustment
111-
| ServiceError::AdjustmentReasonNotValid
112-
| ServiceError::AdjustmentReasonNotProvided => BadUserInput(formatted_error),
118+
| ServiceError::AdjustmentReasonNotValid => BadUserInput(formatted_error),
113119

114120
ServiceError::NewlyCreatedInvoiceDoesNotExist
115121
| ServiceError::StockInLineInsertError(_)

server/graphql/stock_line/src/mutations/insert.rs

+30-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use graphql_core::{
55
standard_graphql_error::{validate_auth, StandardGraphqlError},
66
ContextExt,
77
};
8-
use graphql_types::types::StockLineNode;
8+
use graphql_types::types::{AdjustmentReasonNotProvided, StockLineNode};
99
use repository::StockLine;
1010
use service::{
1111
auth::{Resource, ResourceAccessRequest},
@@ -32,9 +32,16 @@ pub struct InsertInput {
3232
pub item_variant_id: Option<String>,
3333
}
3434

35+
#[derive(SimpleObject)]
36+
#[graphql(name = "InsertStockLineError")]
37+
pub struct InsertError {
38+
pub error: InsertErrorInterface,
39+
}
40+
3541
#[derive(Union)]
3642
#[graphql(name = "InsertStockLineLineResponse")]
3743
pub enum InsertResponse {
44+
Error(InsertError),
3845
Response(StockLineNode),
3946
}
4047

@@ -58,12 +65,20 @@ pub fn insert(ctx: &Context<'_>, store_id: &str, input: InsertInput) -> Result<I
5865
}
5966

6067
fn map_response(from: Result<StockLine, AddNewStockLineError>) -> Result<InsertResponse> {
61-
match from {
62-
Ok(stock_line) => Ok(InsertResponse::Response(StockLineNode::from_domain(
63-
stock_line,
64-
))),
65-
Err(error) => map_error(error),
66-
}
68+
let result = match from {
69+
Ok(stock_line) => InsertResponse::Response(StockLineNode::from_domain(stock_line)),
70+
Err(error) => InsertResponse::Error(InsertError {
71+
error: map_error(error)?,
72+
}),
73+
};
74+
Ok(result)
75+
}
76+
77+
#[derive(Interface)]
78+
#[graphql(name = "InsertStockLineErrorInterface")]
79+
#[graphql(field(name = "description", ty = "&str"))]
80+
pub enum InsertErrorInterface {
81+
AdjustmentReasonNotProvided(AdjustmentReasonNotProvided),
6782
}
6883

6984
impl InsertInput {
@@ -104,14 +119,20 @@ impl InsertInput {
104119
}
105120
}
106121

107-
fn map_error(error: AddNewStockLineError) -> Result<InsertResponse> {
122+
fn map_error(error: AddNewStockLineError) -> Result<InsertErrorInterface> {
108123
use StandardGraphqlError::*;
109124
let formatted_error = format!("{:#?}", error);
110125

111126
let graphql_error = match error {
127+
// Structured Errors
128+
AddNewStockLineError::AdjustmentReasonNotProvided => {
129+
return Ok(InsertErrorInterface::AdjustmentReasonNotProvided(
130+
AdjustmentReasonNotProvided,
131+
))
132+
}
133+
112134
// Standard Graphql Errors
113135
AddNewStockLineError::AdjustmentReasonNotValid
114-
| AddNewStockLineError::AdjustmentReasonNotProvided
115136
| AddNewStockLineError::StockLineAlreadyExists => BadUserInput(formatted_error),
116137
AddNewStockLineError::NewlyCreatedStockLineDoesNotExist
117138
| AddNewStockLineError::LineInsertError(_)

server/graphql/stocktake_line/src/mutations/insert.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use graphql_core::simple_generic_errors::CannotEditStocktake;
66
use graphql_core::standard_graphql_error::{validate_auth, StandardGraphqlError};
77
use graphql_core::ContextExt;
88
use graphql_types::generic_errors::StockLineReducedBelowZero;
9-
use graphql_types::types::StocktakeLineNode;
9+
use graphql_types::types::{
10+
AdjustmentReasonNotProvided, AdjustmentReasonNotValid, StocktakeLineNode,
11+
};
1012
use repository::StocktakeLine;
1113
use service::NullableUpdate;
1214
use service::{
@@ -16,8 +18,6 @@ use service::{
1618
},
1719
};
1820

19-
use super::{AdjustmentReasonNotProvided, AdjustmentReasonNotValid};
20-
2121
#[derive(InputObject)]
2222
#[graphql(name = "InsertStocktakeLineInput")]
2323
pub struct InsertInput {
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,3 @@
1-
use async_graphql::Object;
2-
31
pub mod delete;
42
pub mod insert;
53
pub mod update;
6-
7-
pub struct AdjustmentReasonNotProvided;
8-
9-
#[Object]
10-
impl AdjustmentReasonNotProvided {
11-
pub async fn description(&self) -> &str {
12-
"Stocktake line has no adjustment reason"
13-
}
14-
}
15-
pub struct AdjustmentReasonNotValid;
16-
17-
#[Object]
18-
impl AdjustmentReasonNotValid {
19-
pub async fn description(&self) -> &str {
20-
"Adjustment reason is not valid for adjustment direction"
21-
}
22-
}

0 commit comments

Comments
 (0)