Image capture of Returned Goods during Return
Authors:
Valery Kornilovich, Randy Chan
Changed on:
4 July 2024
Key Points
- This article gives System Integrator (SI) Partners and Businesses a high-level idea of where new features can be built on top of the OMS reference solution to support the customer's requirement within Fluent OMS.
- This idea/solution will provide a new feature where the Store User can take a photo of the returning item and attach it as part of the returnOrder entity.
- The source code will not be provided in this article.
Prerequisites
Steps
Use Case
As a Store associate, I would like to take a photo of the returned item during the return in-store process.
As a Return Storeman in the warehouse, I would like to take a photo of the returned item during the return process.
Solution Approach
- The image capture process needs to occur on this standard screen: return entry/creation screen.
- Camera icon/button against each return order item.
- Pressing this icon/button will present a modal on top of the screen.
- This modal will contain the image capture component.
- Once a photo is captured, the user is presented with the still image.
- The user can choose to ‘ok’ or ‘retake’. Retake removes the previous image and can be replaced by a new image. 'ok' adds the image to that order line.
- The user enters all the standard return details (condition, reason, etc.).

Technical Design Overview
To support the business solution design, the following technical areas need to be enhanced:
- Extend the custom UI component (by using Component SDK) -
`ReturnItemsExtended`
- Customize rule (to save Attributes with image data) -
`CreateReturnOrderFromOrder`
- Setting -
`RETURN_IMAGE_OPTIONS`
Extend on the custom UI Component: ReturnItemsExtended
For training purposes, the camera package can be downloaded here: https://www.npmjs.com/package/react-html5-camera-photo
Create a new file:
`PhotoCapture.tsx.`
This class will provide the PhotoCapture button function.
1 const showModal = (imageUri?: string) => {
2 pushModal({
3 title: translate(
4 'fc.sf.ui.returns.orders.createReturnOrder.list.returnItem.takePicture',
5 ),
6 body: (
7 <div style={{ padding: '20px' }}>
8 {imageUri ? (
9 <img src={imageUri} alt="" className={classes.returnPopupImg} />
10 ) : (
11 <Camera
12 onTakePhotoAnimationDone={handleTakePhotoAnimationDone}
13 onCameraError={() => {
14 setDataUri('');
15 }}
16 idealResolution={{
17 width: 640,
18 height: 480,
19 }}
20 imageType={imageType}
21 imageCompression={+imageCompression}
22 isDisplayStartCameraError={true}
23 />
24 )}
25
26 {imageUri && (
27 <div className={classes.returnPopupBtns}>
28 <Button
29 onClick={() => {
30 updateDataUri();
31 }}
32 variant="contained"
33 color="primary"
34 >
35 {translate(
36 'fc.sf.ui.returns.orders.createReturnOrder.list.returnItem.retake',
37 )}
38 </Button>
39 <Button onClick={clearModals} variant="contained" color="primary">
40 {translate(
41 'fc.sf.ui.returns.orders.createReturnOrder.list.returnItem.ok',
42 )}
43 </Button>
44 </div>
45 )}
46 </div>
47 ),
48 });
49 };
Language: tsx
Name: PhotoCapture.tsx showModal snippet
Description:
PhotoCapture.tsx showModal snippet
1 return (
2 <>
3 <>
4 <IconButton
5 onClick={() => {
6 showModal(dataUri);
7 }}
8 >
9 <AddAPhoto />
10 </IconButton>
11
12 {dataUri && <img src={dataUri} alt="" className={classes.returnImg} />}
13 </>
14 </>
15 );
Language: tsx
Name: PhotoCapture.tsx return snippet
Description:
PhotoCapture.tsx return snippet
Next, add PhotoCapture details into ReturnItemsExtended.tsx
1//when there is an update, add the details into the item attribute
2 if (returnItem.photo && selectedReturnItem) {
3 selectedReturnItem.attributes = [
4 {
5 name: 'returnImage',
6 value: returnItem.photo,
7 type: 'STRING',
8 },
9 ];
10 }
11
12
13// dcreate thshis const as part of the class,
14 const imageCaptureAttr: TranslatableAttribute = {
15 type: 'component',
16 label:
17 'i18n:fc.sf.ui.returns.orders.createReturnOrder.list.returnItem.imageCapture',
18 labelFallback: 'Capture',
19 options: {
20 component: 'fc.photo.capture',
21 props: {
22 id: '{{node.state.orderItemRef}}',
23 onChange: handleImgChange,
24 data: dataWithState?.orderById.items,
25 },
26 },
27 align: 'center',
28 };
29
30 // when there is a change:
31 const handleImgChange = (event: PhotoCaptureEvent) => {
32 const returnItem = state.returnItems.find(
33 (i) => i.orderItemRef === event.id,
34 );
35 if (!returnItem) {
36 console.error(
37 'Error handling quantity selector change: Could not find item with ref ' +
38 event.id,
39 );
40 return;
41 }
42 const returnItemUpdate = { ...returnItem };
43 returnItemUpdate.photo = event.value;
44
45 dispatch({ type: 'update', value: returnItemUpdate });
46 };
47
Language: tsx
Name: add PhotoCapture details into ReturnItemsExtended.tsx
Description:
add PhotoCapture details into ReturnItemsExtended.tsx
Custom Rule
The new rule CreateReturnOrderFromOrder has been added to a custom rule plugin. It's an enhanced version of the basic rule from module-order. Additional code accepts new EventAttributes supported by UI changes and saves them.
Property | Value |
Plugin name | <yourPluginName> |
Rule API Client | GraphQL |
Rule Info Description | Creates a return entity to begin the process of refunding the returned items. |
Supported Entities | ORDER |
1//event attribute changes
2@EventAttribute(name = "returnItems",
3 type = "RETURN_ITEMS")
4@EventAttribute(name = "pickupLocation")
5@EventAttribute(name = "lodgedLocation")
6@EventAttribute(name = "destinationLocation")
7@EventAttribute(name = "type")
8
9//save information about lodged location if it present in "lodgedLocation" or "pickupLocation"
10 boolean hasLodgedLocation = false;
11 String lodgedLocation = null;
12
13 lodgedLocation = tryGetValueForKeyOrNull(pickupLocationMap, "lodgedLocation", "pickupLocation", String.class);
14 if (!isEmptyOrBlank(lodgedLocation)) {
15 hasLodgedLocation = true;
16 }
17
18...
19 if (!hasLodgedLocation) {
20 lodgedLocation = tryGetValueForKeyOrNull(event.getAttributes(), "lodgedLocation", "", String.class);
21 hasLodgedLocation = !isEmptyOrBlank(lodgedLocation);
22 }
23
24
25//save attributes at the end of createReturnItem
26 // append attributes
27 List attributes = tryGetValueForKeyOrNull(returnItemMap, "attributes", "", List.class);
28 if (attributes != null) {
29 List<Attribute> attributesConverted = ValueConverter.convertList(attributes, Attribute.class);
30 returnItemBuilder.attributes(attributesConverted.stream()
31 .map(a -> AttributeInput.builder().name(a.getName()).value(a.getValue()).type(a.getType()).build())
32 .collect(Collectors.toList()));
33 }
34
Language: java
Name: Example required of changes in CreateReturnOrderFromOrder
Description:
Modified CreateReturnOrderFromOrder rule
Update order workflow
Rule CreateReturnOrderFromOrder was updated and added a new type returnItemsImage. To use it workflow has to be updated.
1{
2 "name": "ReturnOrder",
3 "description": "This ruleset is triggered when an event has been sent to initiate a Return against an Order. ",
4 "type": "ORDER",
5 "subtype": "HD",
6 "eventType": "NORMAL",
7 "rules": [
8 {
9 "name": "{{fluent.account.id}}.order.ValidateReturnQty",
10 "props": null
11 },
12 {
13 "name": "{{fluent.account.id}}.{packageName}.CreateReturnOrderFromOrder",
14 "props": null
15 },
16 {
17 "name": "{{fluent.account.id}}.core.SetState",
18 "props": {
19 "status": "RETURN_CREATED"
20 }
21 }
22 ],
23 "triggers": [
24 {
25 "status": "COMPLETE"
26 },
27 {
28 "status": "RETURN_CREATED"
29 },
30 {
31 "status": "RETURN_COMPLETE"
32 }
33 ],
34 "userActions": [
35 {
36 "context": [
37 {
38 "label": "Submit Return",
39 "type": "PRIMARY",
40 "modules": [
41 "adminconsole",
42 "store",
43 "servicepoint"
44 ],
45 "confirm": false
46 }
47 ],
48 "attributes": [
49 {
50 "name": "returnItems",
51 "label": "Items",
52 "type": "returnItemsImage",
53 "source": "",
54 "defaultValue": "",
55 "mandatory": true
56 },
57 {
58 "name": "pickupLocation",
59 "label": "Pickup Location",
60 "type": "ADDRESS",
61 "source": "",
62 "defaultValue": "",
63 "mandatory": false
64 },
65 {
66 "name": "lodgedLocation",
67 "label": "Lodged Location",
68 "type": "STRING",
69 "source": "",
70 "defaultValue": "",
71 "mandatory": false
72 },
73 {
74 "name": "type",
75 "label": "Return Type",
76 "type": "STRING",
77 "source": "",
78 "options": {
79 "active": [
80 {
81 "name": "Default",
82 "value": "DEFAULT"
83 }
84 ]
85 },
86 "defaultValue": "",
87 "mandatory": false
88 }
89 ]
90 }
91 ]
92 }
Language: json
Name: Home Delivery (HD)
Description:
HD workflow changes
1{
2 "name": "ReturnOrder",
3 "description": "Validate if the item can be returned and create the return order",
4 "type": "ORDER",
5 "subtype": "CC",
6 "eventType": "NORMAL",
7 "rules": [
8 {
9 "name": "{{fluent.account.id}}.order.ValidateReturnQty",
10 "props": null
11 },
12 {
13 "name": "{{fluent.account.id}}.{packageName}.CreateReturnOrderFromOrder",
14 "props": null
15 },
16 {
17 "name": "{{fluent.account.id}}.core.SetState",
18 "props": {
19 "status": "RETURN_CREATED"
20 }
21 }
22 ],
23 "triggers": [
24 {
25 "status": "COMPLETE"
26 },
27 {
28 "status": "RETURN_CREATED"
29 },
30 {
31 "status": "RETURN_COMPLETE"
32 }
33 ],
34 "userActions": [
35 {
36 "context": [
37 {
38 "label": "Submit Return",
39 "type": "PRIMARY",
40 "modules": [
41 "adminconsole",
42 "store",
43 "servicepoint"
44 ],
45 "confirm": false
46 }
47 ],
48 "attributes": [
49 {
50 "name": "returnItems",
51 "label": "Items",
52 "type": "returnItemsImage",
53 "source": "",
54 "defaultValue": "",
55 "mandatory": true
56 },
57 {
58 "name": "pickupLocation",
59 "label": "Pickup Location",
60 "type": "ADDRESS",
61 "source": "",
62 "defaultValue": "",
63 "mandatory": false
64 },
65 {
66 "name": "lodgedLocation",
67 "label": "Lodged Location",
68 "type": "STRING",
69 "source": "",
70 "defaultValue": "",
71 "mandatory": false
72 },
73 {
74 "name": "type",
75 "label": "Return Type",
76 "type": "STRING",
77 "source": "",
78 "options": {
79 "active": [
80 {
81 "name": "Default",
82 "value": "DEFAULT"
83 }
84 ]
85 },
86 "defaultValue": "",
87 "mandatory": false
88 }
89 ]
90 }
91 ]
92}
Language: json
Name: Click and Collect (CC)
Description:
CC workflow changes
Setting - RETURN_IMAGE_OPTIONS
1{
2 "imageType": "jpg",
3 "imageCompression": "0.3"
4}
Language: json
Name: RETURN_IMAGE_OPTIONS
Description:
Type: JSON
This setting is for image type and image compression.
imageType can contain ‘jpg’ or ‘png’ type.
imageCompression can contain a number from 0 to 1. 0 - is a maximum compression, 1 - is an original image. imageCompression works only with ‘jpg’ imageType.
Result
The result of the return image functionality enables Store Users to capture a photo of the item being returned. This photo is then attached to the returnOrder entity, providing a visual confirmation of the item's condition at the time of return. This feature enhances the return process by adding a layer of transparency and accountability, which can be beneficial for both the customer and the business.