As a user filling out a Requisition form,
I want to have my Requisition data also populate the Stock Management service in OpenLMIS,
so that I can view my data in the form of Stock Cards and stock events.
This ticket is part of the larger effort outlined in Connecting Stock Management and Requisition Services
In Scope / Acceptance Criteria
When a user initiates any new Requisition, that creates a "Draft Physical Inventory" in the Stock service. Each time the requisition is synced to the server, the Requisition service updates that draft in the Stock service. UPDATE: We will not create a Draft until the Final Approval happens (upon final approval we create a draft and then instantly submit that draft as final. We changed this because having a draft start at Initiation would block any users from creating an Emergency Requisition, because Stock only allows one draft at a time. Note: The UI should not need to worry about talking to the stock service directly when a user works on the Requisition form.
When a user approves a Requisition in OpenLMIS (as of the final approval, not IN_APPROVAL), the data from that Requisition is saved into the Stock service as a final/finished Physical Inventory. (The It makes a physical inventory draft which it then immediately saves becomes Saved.)
Certain fields from the Requisition form map to certain fields or certain reasons within the Stock service:
(B) Total Received Quantity: maps to a Physical Inventory reason called 'Receipts'. Determining the Reason UUID for 'Receipts' can be done by convention or by a .env-type configuration for the Requisition Service to determine which Stock Reason to map this to. These receipts have no 'source' organization. Again, if you want to specify a source, go into Stock UI to record your Receipts. If (B) is not enabled on the requisition (as configured on the program's requisition template at the moment it was initiated), then the Requisition Service will not have any 'Receipts' quantities to map into the Physical Inventory. The same is true when the 'Receipts' reasons does not exist in stockmanagement service and when 'Receipts' reason ID is not given in the requisition service .env file.
(C) Total Consumed Quantity: maps to a Physical Inventory reason called 'Consumed'. Like for (B), determining the reason UUID can be done by convention or by configuration. And like for (B), if field (C) is not enabled, then there are not any 'Consumed' quantities to map into the Physical Inventory. This is true whether or not C is User Input or Calculated. The same is true when the 'Consumed' reasons does not exist in stockmanagement service and when 'Consumed' reason ID is not given in the requisition service .env file
(D) Total Losses and Adjustments: each adjustment maps to a Physical Inventory reason from the reason list. The modal that powers this field is a shared component used by both Requisition form and Stock UI. These are 'aggregate' numbers, a total for the whole period, not 'transactional' numbers with a different date for each (see note about dates next). Like for (B) and (C), if this field is not enabled, then there are not any additional adjustments to map into the Physical Inventory.
(E) Stock On Hand: maps to the Physical Inventory quantity. Unlike the other fields, we will always map (E) to the Physical Inventory quantity, whether or not (E) is displayed on the Requisition form; it may be User Input (and displayed) or Calculated (and either displayed or not), but regardless we must map this quantity.
Overall, there is not a different date for each 'line item'. Instead, the Requisition date that we added in will be the date used for this Physical Inventory, OR if that date is NULL (because it can now be configured to be disabled based on OLMIS-3023), then instead we use the period end date of the Requisition's period. This one date applies to every reason and every product within that Physical Inventory. If users want to do more granular adjustments, they can go into the Stock UI for that, where each line item has its own date.
All of the above is true for both Regular and Emergency requisitions. Both kinds of requisitions map to stock data. That said, in normal usage we expect there will not be Adjustments recorded on Emergency requisitions (usually).
Skipped rows in the requisition will not push any data to Stock service. Skipped products will be 'removed' from the Draft Physical Inventory so they are not considered part of that inventory at all. If they are then "unskipped" when the requisition is edited later, those products need to be added to the draft physical inventory.
The Requisition Form UI must still work offline.
Approving the requisition (the final approval, not IN_APPROVAL) requires that the user be online, and at that moment the Requisition service will call the Stock service to record this data as a completed physical inventory (the OpenLMIS UI app will not need to know to call the Stock service).
Note: Physical Inventories are permanent once written to the stock service; they are Drafts before that. So we only write this as a final/saved Physical Inventory once (at the moment of final Approval).
When any user later views the same requisition (after final Approval) it will be read-only. At this point, it is impossible to "edit" that data that got written to Stock Service. Even admins cannot edit these quantities after final Approval.
If (somehow) the Requisition service violated its current rules and later on someone DID have permissions to edit the Requisition, that data would still not be editable in Stock service. So the Requisition service should throw an error if somehow it tries to re-save a Requisition that contains a Saved/Final Physical Inventory rather than a Physical Inventory Draft.
At the time of final approval when the Requisition service saves to Stock service, if the operation throws an error it will need to respond with a helpful error message, Requisition will need to pass that error to the UI, and the UI will need to allow the user to correct their adjustments and try Authorizing again. We probably want to have Requisition service save to Stock service before it marks the requisition as Authorized Final Approved, so if the Stock save fails with an error the Requisition status will not be updated and will still allow the user to edit it to fix the data.
When a user initiates a new Requisition, that creates a "Draft Physical Inventory" (stated above). Be warned that the Stock service can only handle having one draft physical inventory at a time in its system. So if there is still a draft, a user cannot initiate another Requisition. That will prevent any Regular or Emergency requisitions from being initiated at any time there is another Requisition that is anywhere in the workflow (from Initiated anywhere up to Final Approved). If a user tries to initiate give final approval on a requisition while Stock Management has a Draft Physical Inventory, then the Requisition Service will throw an error when it tries to call Stock Service to create that new Draft. That error should bubble up to the UI so the user knows why.
The stock service will stop submission of data if it would cause a quantity to go below zero. That should never happen here, but it actually is possible if an implementation is using both the Requisition Form UI and the Stock UI to record transactions. When they fill out the Requisition, column (E) has to be zero or positive, and columns (B) (C) and (D) have to add up. So far so good. HOWEVER, the discrepancy that they are providing reasons for is based on the discrepancy from the previous SOH quantity from the previous Physical Inventory that the stock service was aware of at that instant they are filling out the Requisition. But the requisition may be editable over a matter of many days. If someone records more stock transactions during that period, then the Total Losses and Adjustments on this Requisition Form might need to account for a different discrepancy quantity. So basically, if this causes the Stock Service to have a quantity that goes below zero which makes Stock throw an error message, we need to have Requisition send an error message and have the UI explain this to the user so they can correct the issue.
Another edge case is what happens if a user goes into Stock UI and deletes their draft Physical Inventory. If that happens, next time the Requisition is saved it would need to create a new Draft Physical Inventory to put the quantities into. This should now be less of an issue because the Requisition service will only create a Draft right at the Final Approval step (see edits above).
Another edge case is if a user goes into the Stock UI, opens the Draft Physical Inventory, and actually changes the numbers there. Those changes will not be reflected on the Requisition. So if a user changes Receipts quantity from 10 to 20, next when they open the Requisition the quantity will still be 10. And if they save that Requisition again, the draft physical inventory will be re-saved with 10 (and 20 will be over-written). This is a 1-way data sync from Requisition to Stock; Requisition does not pull numbers from Stock. For now, that is just a limitation we will need to document. This becomes a 2-way sync in future phases. Again, this is now not an issue because going into Stock UI, users will not see any Draft Physical Inventory to edit. We have added ideas to the wiki page for addressing this after 3.2.0/after Phase 2.
'Receipts' and 'Consumed' reasons should be added to the Stock Management service bootstrap data.
Not included in this ticket:
We may write a separate ticket to create stock events for historical requisitions that were pre-existing prior to this upgrade. This ticket itself (2834) will only push data into the stock service when requisitions are approved going forward.
Update: Brandon discussed this with and Josh, and we agreed this is not critical now before 3.2.0. Brandon added a new row into the wiki for Phase 2.
Code the Requisition Service to push data to Stock Service as specified above.
Test all the edge cases/error conditions listed above (developers and QA may want to talk together to identify which are tested in code or unit tests versus manual testing).
Additional edge case to verify:
Requisition P is initiated when Reason X is active for this program. Next, an administrator de-activates Reason X or un-assigns it from the program. After that, the user continues working on Requisition P which still has the snapshot list. User adds some adjustments for product quantities using Reason X. The user is able to save their requisition, and even Submit it. But later when the Requisition is approved (final approval), the Requisition service will try to push those adjustment quantities to the Stock service. The Stock service will respond with an error something like "Cannot save stock information: the reason ___ is not active for program ___. Update your data to use an active reason and try again." At that point, they might need to reject the requisition so the facility staff can update it to change their adjustments to use the current stock reasons, not Reason X. Otherwise they will not be able to move the requisition to the final approval status. This edge case will not be implemented until the future ticket In Progress where we actually push data into Stock Service. But for now in this ticket we still want to test that even when the list of valid reasons changes, the Requisition still keeps the Snapshot list from the time it was Initiated.
Good question. I suggest that Bootstrap data would be a good place to add 'Receipts' and 'Consumed' reasons into stock service. That would be good to have in Bootstrap since we do expect most implementations will want to have those 2 reasons in order to configure in the .env file so that Requisition columns (B) and (C) can map to those reasons.
Regarding what happens if those reasons do not exist:
It should be the same thing as if (B) or (C) is not enabled on the requisition. See the underlined text in the ticket description for (B) and (C). In other words, if the reason does not exist in stock management, or if the Requisition .env file does not have a UUID configured for mapping (B) or (C), then the Requisition Service will not map the 'Receipts' or 'Consumed' quantity into the Physical Inventory.
I've added an additional edge case from
Regarding the new issue raised in a Slack #help thread, we discussed this today. The issue is that Stock Management service currently requires that a Physical Inventory record must contain all products that have a stock card for the program & facility. This includes all Lots' stock cards when there are lots. This validation happens both in the UI and also in the API.
The Proposed Solution from me, and is this:
Change the Stock Management API to relax the validation rule(s) that require every stock card to have a physical inventory value provided.
Leave the UI validation in place as is.
This means that for now implementations who use the Stock UI to conduct a physical inventory will still need to enter a value for every single Product and Lot.
Anyone using the API (including our Requisition service connection feature) will be able to skip products without getting an error. In addition, they will be able to submit without providing quantities for Lot-based stock cards.
Update the unit tests in stock management for this change (and please include Team ILL on a review for that change).
During manual testing of this ticket, add a test case for skipping line items in a Requisition and making sure it is possible to move the Requisition through the work flow. It will push data into the stock service (with no error), and then the stock cards for products that were skipped simply show nothing at all, whereas all the other stock cards show the new data that was pushed from the Requisition. FYI
After 3.2.0, we need to come back and deal with this situation further. Mary Jo made a ticket for that.
note that fields can be hidden and still calculate in requisitions (i.e.) stock on hand can be hidden.
We must still send the adjustment in this case - so that the requisition is possible to approve.
I checked, and the feature works correctly for both programs and types of requisitions, as well as with all fields enabled and with disabled fields.