Eswatini CMIS/eLMIS Integration facilitation
Objective
The integration aims to streamline and automate the communication of dispensing information between CMIS (Dispensing System) and eLMIS (Inventory Management System). This will ensure accurate stock adjustments in eLMIS, reducing the stock on hand based on actual dispensed quantities.
See:
Open questions
Does the CMIS Team need information about lot expiration?
How much of sorting/filtering capabilities would be required by CMIS Team in the Stock On Hand read endpoint?
Core solution idea
To accommodate 3rd party integration, we shall extend the Stockmanagment API for Stock Event creation to allow reference facility, orderable, program, lot, and reason through business keys (ex.: unique names, codes, etc). Additionally, the error responses shall be improved to clearly state the reason for failure, ex.: in case of an event that consumes stock, the error object shall contain information about the current stock to clearly state why the requested amount can not be documented as consumed.
Requirements
The extension shall not break the current API - not a breaking change.
Issues
Lot’s business key is a combination of Lot code and Trade Item (having unique gtin).
We can deduce the correct Lot based on Orderable. Lot is assigned to TradeItem which is assigned to all versions of the Orderable - Lot appears to ignore Orderbale versioning. The StockCard ignores Orderable versioning.
Therefore only a Lot code is required.
The orderable referenced by code would always point to the latest version of the orderable.
Which is fine in the context of stock management.
Therefore only an orderable code is required.
The Stock Event Line item references the source and destination via Node which may be a Facility or Organization - a Node has no business key.
This can be solved by priority lookup - assume it’s Facility Code if not found then assume it’s an Organization name, else an error that clearly says that Facility or Organization was not found. It should be fine since having the Facility with a code that is the same as the name of some organization is a bad configuration and doesn't make sense. Both are optional.
Therefore only a facility code is required to reference source and destination.
The Stock Event requires OLMIS User to be documented. The user is usually inferred from the login session. In the case of 3rd party integration, an API Key authentication is used that has no related user.
Therefore, as a temporary solution, we shall remove the not-null constraint on the StockEvent.userId. A proper solution would involve answering questions like “Why do we need the user documented?”, “Do we create users for each API Key or one for Integration API?”, “Having API Key User would allow us an easy way to have a more fine-grained control over access that 3rd party has, but would require some refactorings among all services”.
Building a public API Gateway service for OLMIS could be more beneficial in the long run. A microservice, optimized for 3rd party access, maybe own rate limiting, caching, load balancing, decoupled from internals of OLMIS, etc.
Too much work/unknown for now.
Technical details
We make a 3rd-party-friendly API that is an alternative to an internal StockEventsController
.
Introduce the idea of
/api/public
REST API namespace for OpenLMIS. All REST APIs inside this namespace are made with 3rd party integrations in mind. They are not supposed to be used by OLMIS services. This shall be viewed as a namespace for API Gateway endpoints. Mind the Nginx configuration.Remove not null constraint from
StockEvent.userId
.Create a new API endpoint -
POST /api/public/stockEvents
- which is an alternative forPOST /api/stockEvents/
that resolves references by business keys (with some smart-guessing logic). There is a performance cost expected, but it should be small. It should work on top of existing Stock Event logic.POST /api/public/stockEvents Headers: Content-Type: application/json Authorization: Bearer <token> Body: { "facility": "H093", // required, code of Facility (department) where event happens "program": "tb", // required, code of Program where event happens (all medications are assigned to this program) "signature": "free text", // optional, free text field, usually for operator's name "documentNumber": "free text", // optional, free text field, a ref // optional, free text field, a reference document "items": [ // required, list of stock changes "orderable": "102287", // required, product's code "lot": "A307094", // required, lot code "occurredDate": "2024-12-10", // required, a full ISO representation of timestamp must be accepted too, OLMIS's stock events are limited to date be design "quantity": 1, // required, product's quantity "reason": "Dispensed", // required, a name of stock movement reason "destination": "" // optional, code of Facility (department), where the stock moved out "source": "" // optional, code of Facility (department), from where the stock moved in ] }
The <token> can be obtained as described below.
There is no logic for signature and documentNumber, both can be used for Eswatini LMIS/CMIS integration-specific data, ex.: doctor’s name, and prescription code. It can be accessed by the reporting feature later.
The occuredDate contains only the date part, the API shall support full timestamp ISO format too. The OLMIS design makes it so only a date part is meaningful.
The reason defines whether stock should be reduced or increased. It’s configurable, CMIS Team can choose the name, and it’s going to be configured in the Eswatini instance.
The destination and source are used for stock movement into the facility and from the facility to document where it went and where it came from. Eswatini LMIS/CMIS Integration shall ignore these.
Improve error handling for StockEvent validation. The
LocalizedMessage
shall be extended with context data that can be automatically and reliably interpreted. The newdetails
object shall be added which would contain error-specific details, ex:Response: { "messageKey": "stockmanagement.adjustment.quantity.invalid", "message": "Stock Adjustment quantity must not be negative.", "details": { "requestedQuantity": 15, "availableQuantity": 10, "orderable": "102287", "lot": "A307094" } }
Create a new API endpoint
GET /api/public/stockOnHand
. It’s a more integration-friendly option forGET /api/v2/stockCardSummaries
. The endpoint shall return onlyactive=true
Lots, that at the time of request havestockOnHand > 0
and are not expired. The response shall be sorted by product code.GET /api/public/stockCardSummaries?facility=H093&program=tb&page=0&size=10 Headers: Authorization: Bearer <token> Query Parameters: facility: required, code of Facility (department) where stock is tracked program: required, code of where stock is trackeds (all medications are assigned to this program) page: required, page number counting from 0 size: required, page size code: optional, search term for orderable filter Response: { "content": [ { "orderable": "102287", // product's code "totalStockOnHand": 21, // total stock on hand, sum of all lots "stockCards": [ // name comes from OLMIS internals { "lot": "A307094", // lot's code "orderable": "102287", // product's code "stockOnHand": 11, // stock on hand of this lot "occurredDate": "2024-11-04", // date of the last update }, ... ] }, ... ], "empty": false, // shortcut to determine if there is no items to show "first": true, // shortcut to determine if this is 1st page "last": fasle, // shortcut to determine if this is last page "number": 0, // page number, counting from 0 "numberOfElements": 10, // the actual number of elements on the page (can be lower then page size) "size": "10", // page size "totalElements": "18", // total number of elements "totalPages": "2", // total number of pages "pageable": {} // part of OLMIS default response, to be ignored by integration }
The <token> can be obtained as described below.
The code search term limits top-most orderables (items of
content
array) to orderables whose code contains the search term (o.code like ‘%searchterm%’).The paging (size, totalElements, etc) relates to the number of items in the
content
array.The orderable is repeated in items of
content
arrays and items ofstockCards
array because of OLMIS Configuration capabilities - in Eswatini OLMIS these two codes will always be the same.
Additional information
OLMIS Authentication
OLMIS handles authentication via Bearer tokens. The go-to method for 3rd-party integration to access OLMIS is using API Keys.
The API Key has admin-level access to the system.
The token can be obtained in the following way, performed during the initial setup:
Log in to the OLMIS System with an admin-privilege user.
Go to Administration, Service Accounts, Add an account.
The “Key” column contains token to be used in authorization headers.
OpenLMIS: the global initiative for powerful LMIS software