Manual Fulfillment Reassignment
Authors:
Alan Jackson, Sergey Chebotarev, Randy Chan, Valery Kornilovich
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 enter the rejection reason code during the Pick Confirm Process.
- This feature allows customer service to reroute an existing to a specific location that can fulfill the order.
`fulfilment`
Prerequisites
Steps
Use Case
As a Customer Service agent, I want to be able to manually reroute an existing fulfillment to a new fulfillment location.
Solution Approach
The ability to override the standard sourcing logic for
`fulfilment`
`fulfilment`
The store manager should have visibility into the inventory position of a specific location when deciding whether to fulfill from another location. For example, store 1 has 5 inventory, and store 2 has 1 inventory position. The store manager should select the location with more inventory.
By default, locations that are near the existing location that was chosen for
`fulfilment`
The following functional aspects need to be considered:
- There is functionality to select an alternative location to fulfill the already existing in the order.
`fulfilment`
- Alternative locations that are near the existing location that was chosen for should be displayed.
`fulfilment`
- There should be a manual override to enter a zip code to determine alternative locations that can be selected if they are too far away.
Technical Design Overview
To support the business solution design, the following technical areas need to be enhanced:
- A new custom UI component (by using Component SDK) -
`StoreSelector`
- A new custom rule (by using Rule SDK) -
`AssignFulfilmentToLocation`
- Add the new ruleSet: into Order workflows
`ManuallyReassign`
Customised UI Component: StoreSelector
In the file:
`StoreSelector.tsx`
1const searchVirtualInventoryQuery = `query availability($productQuantityInput: [ProductQuantityInput!]!, $locationRef: String!, $lat: Float!, $lng: Float!, $virtualCatalogueRef: String!) {
2 searchVirtualInventory(
3 virtualCatalogue: { ref: $virtualCatalogueRef },
4 productQuantities: $productQuantityInput,
5 excludedLocationRefs: [$locationRef],
6 orderByProximity: { longitude: $lng, latitude: $lat }
7 ) {
8 edges {
9 node {
10 location {
11 ref
12 name
13 primaryAddress {
14 postcode
15 }
16 }
17 virtualPositions { quantity }
18 }
19 }
20 }
21 }`;
22
23
24 -- then create a function whihc need to contain below and this function is part of the useEffect:
25 --
26 await getQuery<SLResult>(searchLocationQuery, {
27 fulfilmentId: entityContext?.[0].entity.id,
28 }).then((locationWithOrder) => {
29 locationWithOrder.data?.fulfilmentById.order.items.edges.map(
30 (item: any) => {
31 orderItems.push({
32 productRef: item.node.ref,
33 quantity: item.node.quantity,
34 });
35 },
36 );
37 latitude = locationWithOrder.data?.fulfilmentById.fromAddress.latitude;
38 longitude = locationWithOrder.data?.fulfilmentById.fromAddress.longitude;
39 });
40
41 const stores: storeInterface[] = [];
42
43 await getQuery<SVIResult>(searchVirtualInventoryQuery, {
44 productQuantityInput: orderItems ? orderItems : '',
45 locationRef: auth.context.current.details?.ref,
46 lat: latitude,
47 lng: longitude,
48 virtualCatalogueRef: `BASE:${auth.user.primaryRetailer?.id}`,
49 })
50 .then((storesData) => {
51 storesData.data?.searchVirtualInventory.edges.map((item) => {
52 stores.push({
53 name: item.node.location.name,
54 ref: item.node.location.ref,
55 postcode: item.node.location.primaryAddress.postcode,
56 });
57 });
58 setLoading(false);
59 })
60 .catch(() => {
61 setLoading(false);
62 });
63
64const searchLocationQuery = `query fulfilment($fulfilmentId: ID!){
65 fulfilmentById(id: $fulfilmentId) {
66 fromAddress {
67 latitude
68 longitude
69 }
70 order {
71 items {
72 edges {
73 node {
74 ref
75 quantity
76 }
77 }
78 }
79 }
80 }
81 }`;
Language: tsx
Name: Use `getQuery` (from the hook) to get `searchVirtualInventory` and `fulfilmentById`.
Description:
Use getQuery (from the hook) to get searchVirtualInventory and fulfilmentById.
1 return (
2 <>
3 {!loading ? (
4 <Autocomplete
5 options={stores}
6 getOptionLabel={(option) =>
7 `${option.name}, ${option.postcode} (${option.ref})`
8 }
9 onChange={(_event, newStore) => setSelectedOption(newStore)}
10 getOptionSelected={(option, value) => option.ref === value.ref}
11 renderInput={(params) => (
12 <TextField {...params} label="Stores" variant="outlined" />
13 )}
14 />
15 ) : (
16 <Loading />
17 )}
18 </>
19 );
Language: tsx
Name: The sample return snippet:
Description:
The sample return snippet:
Custom Rules
AssignFulfilmentToLocation
Property | Value |
Plugin name | <yourPluginName> |
Rule API Client | GraphQL |
Rule Info Description | Assigns
|
Supported Entities | FULFILMENT |
Event Attributes
Attribute Name | Description |
locationRef | Provided
|
Add a new ruleset in the ORDER workflow (ManuallyReassign)
Several rulesets should be added to Home Delivery (HD) and Click and Collect (CC) Order Workflows to activate the 'Reassign Fulfilment' feature. Every ruleset should contain three rules:
- AssignFulfilmentToLocation: creates new for the selected location
`fulfilment`
- SetState: moves current into MANUALLY_REASSIGNED status
`fulfilment`
- SendEventToUpdateInventoryQuantity: initiates event with UNRESERVE operation for the current inventory catalog to process reservations for the current
`UpdateInventoryQty`
.`fulfilment`
Typical trigger statuses for the ruleset are AWAITING_WAVE, ASSESSMENT, REJECTED, and ESCALATED.
The ruleset must contain a user action to fill in the attribute
`locationRef`
`storeSelector`
All rulesets have type FULFILMENT, but subtypes are different: CC_PFS, CC_PFDC, HD_PFS, HD_PFDC.
1{
2 "name": "ManuallyReassign",
3 "type": "FULFILMENT",
4 "subtype": "CC_PFDC",
5 "eventType": "NORMAL",
6 "rules": [
7 {
8 "name": "{{fluent.account.id}}.{packageName}.AssignFulfilmentToLocation",
9 "props": {}
10 },
11 {
12 "name": "{{fluent.account.id}}.core.SetState",
13 "props": {
14 "status": "MANUALLY_REASSIGNED"
15 }
16 },
17 {
18 "name": "{{fluent.account.id}}.order.SendEventToUpdateInventoryQuantity",
19 "props": {
20 "eventName": "UpdateInventoryQty",
21 "operation": "UNRESERVE",
22 "inventoryCatalogueRef": "DEFAULT:{{fluent.retailer.id}}"
23 }
24 }
25 ],
26 "triggers": [
27 {
28 "status": "AWAITING_WAVE"
29 },
30 {
31 "status": "ASSESSMENT"
32 },
33 {
34 "status": "REJECTED"
35 },
36 {
37 "status": "ESCALATED"
38 }
39 ],
40 "userActions": [
41 {
42 "context": [
43 {
44 "label": "MANUALLY REASSIGN",
45 "type": "SECONDARY",
46 "modules": [
47 "adminconsole",
48 "servicepoint"
49 ],
50 "confirm": false
51 }
52 ],
53 "attributes": [
54 {
55 "name": "locationRef",
56 "label": "Location Ref",
57 "type": "storeSelector",
58 "source": "",
59 "defaultValue": "",
60 "mandatory": false
61 }
62 ]
63 }
64 ]
65 },
Language: json
Name: ManuallyReassign ruleset in Order workflow
Description:
ManuallyReassign ruleset in Order workflow
Result
The User Action would look like (closed/open states):

After the user action has been completed, the existing
`fulfilment`
`MANUALLY_REASSIGNED`
`fulfilment`