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

  1. Does the CMIS Team need information about lot expiration?

  2. 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

  1. The extension shall not break the current API - not a breaking change.

Issues

  1. Lot’s business key is a combination of Lot code and Trade Item (having unique gtin).

    1. 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.

  2. The orderable referenced by code would always point to the latest version of the orderable.

    1. Which is fine in the context of stock management.
      Therefore only an orderable code is required.

  3. 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.

    1. 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.

  4. 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.

    1. 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”.

  5. 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.

    1. Too much work/unknown for now.

Technical details

We make a 3rd-party-friendly API that is an alternative to an internal StockEventsController.

  1. 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.

  2. Remove not null constraint from StockEvent.userId.

  3. Create a new API endpoint - POST /api/public/stockEvents - which is an alternative for POST /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 ] }
    1. The <token> can be obtained as described below.

    2. 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.

    3. 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.

    4. 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.

    5. 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.

  4. Improve error handling for StockEvent validation. The LocalizedMessage shall be extended with context data that can be automatically and reliably interpreted. The new details 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" } }
  5. Create a new API endpoint GET /api/public/stockOnHand. It’s a more integration-friendly option for GET /api/v2/stockCardSummaries. The endpoint shall return only active=true Lots, that at the time of request have stockOnHand > 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 }
    1. The <token> can be obtained as described below.

    2. The code search term limits top-most orderables (items of content array) to orderables whose code contains the search term (o.code like ‘%searchterm%’).

    3. The paging (size, totalElements, etc) relates to the number of items in the content array.

    4. The orderable is repeated in items of content arrays and items of stockCards 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:

  1. Log in to the OLMIS System with an admin-privilege user.

  2. Go to Administration, Service Accounts, Add an account.

  3. The “Key” column contains token to be used in authorization headers.

OpenLMIS: the global initiative for powerful LMIS software