Create StockMovement with ListItems

Hi guys,

I’m trying to create a StockMovement with Line Items through api.
The request looks like this:

{
  "name": "test01",
  "description": "test01",
  "origin": {
    "id": "1"
  },
  "destination": {
    "id": "8a8280847bb2c8bb017bb5df9b1d0002"
  },
  "dateRequested": "10/19/2022",
  "requestedBy": {
    "id": "8a8280847bc0cedf017bc1af1e240001"
  },
  "lineItems": [
    {
      "quantityRequested": 4,
      "sortOrder": 0,
      "recipient": null,
      "product": {
        "id": "8a8280847bad6572017bad6a6d980003"
      }
    },
    {
      "quantityRequested": 8,
      "sortOrder": 0,
      "recipient": null,
      "product": {
        "id": "8a8280847bad6572017bad6a6dc00004"
      }
    }
  ]
}

But I got an error:

{
  "errorCode": 400,
  "errorMessage": "Validation error. Invalid stock movement:\n- Field error in object 'org.pih.warehouse.api.StockMovement' on field 'lineItems': rejected value [[{\"product\":{\"id\":\"8a8280847bad6572017bad6a6d980003\"},\"sortOrder\":0,\"quantityRequested\":4,\"recipient\":null},{\"product\":{\"id\":\"8a8280847bad6572017bad6a6dc00004\"},\"sortOrder\":0,\"quantityRequested\":8,\"recipient\":null}]]; codes [typeMismatch.org.pih.warehouse.api.StockMovement.lineItems,typeMismatch.lineItems,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [org.pih.warehouse.api.StockMovement.lineItems,lineItems]; arguments []; default message [lineItems]]; default message [Failed to convert property value of type 'org.codehaus.groovy.grails.web.json.JSONArray' to required type 'java.util.List' for property 'lineItems'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org.codehaus.groovy.grails.web.json.JSONObject] to required type [org.pih.warehouse.api.StockMovementItem] for property 'lineItems[0]': no matching editors or conversion strategy found]\n",
  "errorMessages": [
    "Failed to convert property value of type org.codehaus.groovy.grails.web.json.JSONArray to required type java.util.List for property lineItems; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org.codehaus.groovy.grails.web.json.JSONObject] to required type [org.pih.warehouse.api.StockMovementItem] for property lineItems[0]: no matching editors or conversion strategy found"
  ]
}

But when I try to create it without LineItems(empty array) it creates it successfuly.

Do you know what could the problem?
Does API not support creating StockMovement with LineItems included in it in one request?

Also I tried to call another api(~/stockmovenets/id/updateItems) call to create LineItems for already created Stockmovennt and it works but only if I set “product.id”: “productId” in JSON for LineItem object. But it fails when I pass product id as a json like: “product” : {“id”: “productId”}

Anyway, how can I properly create StockMovement with LineItems in it?
Thanks

Thanks for the message @Andrii.

I’ll look into whether we can create it all in one request using the stock movement create endpoint. Based on the API docs (Stock Movement - OpenBoxes) it should be possible but it’s been awhile since I wrote those docs. The error suggests that the unmarshalling isn’t working properly.

Failed to convert property value of type org.codehaus.groovy.grails.web.json.JSONArray to required type java.util.List for property lineItems; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org.codehaus.groovy.grails.web.json.JSONObject] to required type [org.pih.warehouse.api.StockMovementItem] for property lineItems[0]: no matching editors or conversion strategy found"]

For now, you can create it in two steps as you suggested. In fact, the stock movement workflow does it in two steps: Create the “header” object (i.e. stock movement) then the line items.

In the case of the product object, we usually flatten some objects into attributes (i.e. “product.id”) but I believe it should handle an object as well.

As an example, here’s a sample request from step 2 of the stock movement workflow. I generated this by adding two line items and clicking the Save button.

URL

POST https://obdev.pih-emr.org/openboxes/api/stockMovements/ff80808171148ca30171876a9dca4f15/updateItems

Payload

{
  "id": "ff80808171148ca30171876a9dca4f15",
  "lineItems": [
    {
      "product.id": "10282",
      "quantityRequested": "22",
      "recipient.id": "",
      "sortOrder": 100
    },
    {
      "product.id": "09f5e11676efa8a20176f1f0235b0793",
      "quantityRequested": "100",
      "recipient.id": "",
      "sortOrder": 200
    }
  ]
}

I just tried to create the stock movement header and line items in one request and I’m receiving the same error. So I guess it can’t be done at the moment.

My assumption is that the create endpoint is using Grails default data binding mechanism (the argument passed to the create closure is unmarshalled by Grails.

def create = { StockMovement stockMovement ->
    ...
}

… while in the updateItems endpoint we’re doing the data binding ourselves using a helper method.

def updateItems = {
    StockMovement stockMovement = stockMovementService.getStockMovement(params.id)
    bindStockMovement(stockMovement, request.JSON)
    stockMovement = stockMovementService.updateItems(stockMovement)
    render([data: stockMovement] as JSON)
}

void bindStockMovement(StockMovement stockMovement, JSONObject jsonObject) {
    // Remove attributes that cause issues in the default grails data binder
    List lineItems = jsonObject.remove("lineItems")
    
    // doing other useful things and stuff ...

    // Bind the rest of the JSON attributes to the stock movement object
    log.debug "Binding line items: " + lineItems
    bindData(stockMovement, jsonObject)

    // Need to clear the existing line items so we only process the modified ones
    stockMovement.lineItems.clear()

    // Bind all line items
    if (lineItems) {
        log.info "binding lineItems: ${lineItems}"
        bindLineItems(stockMovement, lineItems)
    }
}

I am guessing it would not be too much trouble to change the create endpoint to use the bindStockMovement data binding helper method.

But again, for now just use the two-phased approach.

Thanks Justin for the answer.

Yes, that’s what I’m doing. Using the 2-phased approach. I was just thinking maybe I’m doing something incorrectly and there is a better way.