Trouble updating array with information from another array

"I'm encountering an issue with updating an array in costAdjustments. The current code is setting the LengthOfMonth field to 6 for all items in the array, but I only want to update specific entries."

a!save(
  local!newEstimates2,
  local!newEstimates2: a!forEach(
    items: local!newEstimates,
    expression: a!update(
      fv!item,
      "costAdjustments",
      a!forEach(
        items: fv!item.costAdjustments,
        expression: a!update(fv!item, "LengthOfMonth", 6)
      )
    )
  )
)

This isn't exactly what I need. What I want to do is loop through newEstimates and pull data from local!uniqueContractPeriods, which contains three rows of data with the fields: period, periodType, startDate, endDate, noOfMonths, and noOfDays. When the contractPeriodNum in costAdjustments within newEstimates matches the period in uniqueContractPeriods, I want to update the LengthOfMonth in newEstimates with the value of noOfMonths from uniqueContractPeriods. Below is the code I've written.

a!save(  
              local!newEstimates2,  
              local!newEstimates2: a!forEach(  
                items: local!newEstimates,  
                expression: a!update(  
                  fv!item,  
                  "costAdjustments",  
                  a!forEach(  
                    items: local!uniqueContractPeriods,  
                    expression: if(  
                      fv!item.period = fv!item.costAdjustments.contractPeriodNum,  
                      a!update(  
                        fv!item,  
                        "LengthOfMonth",  
                        index(  
                          local!uniqueContractPeriods.noOfMonths,  
                          where(  
                            tostring(fv!item.period) = tostring(fv!item.costAdjustments.contractPeriodNum)  
                          )  
                        )  
                      ),  
                      fv!item  
                    )  
                  )  
                )  
              )  
            )

I am sure it is a simple fix, but I have been working on it for a long time, any assistance would be great.  Thank you.

  Discussion posts and replies are publicly visible

Parents
  • a!save() only executes in the context of a form component responding to a user interaction.  It's unclear whether you're working within this constraint correctly or not.

    Separately, both of your a!save() statements from your code boxes are constructed incorrectly.  Remember that the context of an a!save() statement has 2 parameters, "target" followed by "value", where the "data" parameter is evaluated then saved into the local or rule input variable named in "target".

    However in your code here, you name the target, "local!newEstimates2" in both cases, but then your "value" parameter nonsensically repeats "local!newEstimates2" as if it's a local variable declaration.  This both A) won't do anything, and B) should probably break (though i haven't tried).

    The "correct" way to do it, just to be clear, in the case of your top code example, would look more like this:

    a!save(
      local!newEstimates2,
      a!forEach(
        items: local!newEstimates,
        expression: a!update(
          fv!item,
          "costAdjustments",
          a!forEach(
            items: fv!item.costAdjustments,
            expression: a!update(fv!item, "LengthOfMonth", 6)
          )
        )
      )
    )

    This is a tad easier to visualize in terms of correct context if we flesh out the a!save() statement with parameter names (though this is of course optional) -

    a!save(
      target: local!newEstimates2,
      value: a!forEach(
        items: local!newEstimates,
        expression: a!update(
          fv!item,
          "costAdjustments",
          a!forEach(
            items: fv!item.costAdjustments,
            expression: a!update(fv!item, "LengthOfMonth", 6)
          )
        )
      )
    )

  • Mike,  Thanks so much for a reply.  I got it to work but it still needs some changes.  I am kind of surprised that this worked.  I was trying to place all the updates within curly braces and it only worked for one update.  Can you look and suggest changes?  Thanks in advance for your help and suggestions.

    a!save(
                  local!newEstimates2,
                  a!forEach(
                    items: local!newEstimates,
                    expression: if(
                      a!isNullOrEmpty(
                        wherecontains(
                          fv!item.costAdjustments.contractPeriodNum,
                          tointeger(local!uniqueContractPeriods.period)
                        )
                      ),
                      fv!item,
                      a!update(
                        fv!item,
                        "costAdjustments",
                        a!update(
                          a!update(
                            a!update(
                              a!update(
                                fv!item.costAdjustments,
                                "lengthOfMonth",
                                cast(
                                  1,
                                  index(
                                    local!uniqueContractPeriods.noOfMonths,
                                    wherecontains(
                                      fv!item.costAdjustments.contractPeriodNum,
                                      tointeger(local!uniqueContractPeriods.period)
                                    ),
                                    null
                                  )
                                )
                              ),
                              "periodStartDate",
                              index(
                                local!uniqueContractPeriods.startDate,
                                wherecontains(
                                  fv!item.costAdjustments.contractPeriodNum,
                                  tointeger(local!uniqueContractPeriods.period)
                                ),
                                null
                              )
                            ),
                            "periodEndDate",
                            index(
                              local!uniqueContractPeriods.endDate,
                              wherecontains(
                                fv!item.costAdjustments.contractPeriodNum,
                                tointeger(local!uniqueContractPeriods.period)
                              ),
                              null
                            )
                          ),
                          "LengthOfDay",
                          index(
                            local!uniqueContractPeriods.noOfDays,
                            wherecontains(
                              fv!item.costAdjustments.contractPeriodNum,
                              tointeger(local!uniqueContractPeriods.period)
                            ),
                            null
                          )
                        )
                      )
                    )
                  )
                )

  • Your code has a bunch of inefficiencies that make it difficult to parse (or really even read), let alone sacrifice processing power and efficiency.

    For instance: how many times are you manually re-doing the same identical "wherecontains(fv!item.cost..., tointeger(...))" statement?  I threw this into notepad and counted at least 5 instances of the exact same thing.  You should be abstracting things like this behind local variables, for 2-fold reasons: first, it means you only need to execute the code once, and second (perhaps more importantly), it cuts down on some of the hard-to-read code clutter.

    a!save(
      local!newEstimates2,
      
      a!forEach(
        items: local!newEstimates,
        expression: a!localVariables(
        
          /* do this calculation here, instead of 5 separate times for no reason */
          local!currentContractPeriodIndices: wherecontains(
            fv!item.costAdjustments.contractPeriodNum,
            tointeger(local!uniqueContractPeriods.period)
          ),
          
          if(
            a!isNullOrEmpty(local!currentContractPeriodIndices),
            fv!item,
            
            a!update(
              fv!item,
              "costAdjustments",
              a!update(
                a!update(
                  a!update(
                    a!update(
                      fv!item.costAdjustments,
                      "lengthOfMonth",
                      cast(
                        1,
                        index(
                          local!uniqueContractPeriods.noOfMonths,
                          local!currentContractPeriodIndices,
                          null
                        )
                      )
                    ),
                    "periodStartDate",
                    index(
                      local!uniqueContractPeriods.startDate,
                      local!currentContractPeriodIndices,
                      null
                    )
                  ),
                  "periodEndDate",
                  index(
                    local!uniqueContractPeriods.endDate,
                    local!currentContractPeriodIndices,
                    null
                  )
                ),
                "LengthOfDay",
                index(
                  local!uniqueContractPeriods.noOfDays,
                  local!currentContractPeriodIndices,
                  null
                )
              )
            )
          )
        )
      )
    )

    ...and further,
    a!save(
      local!newEstimates2,
      
      a!forEach(
        items: local!newEstimates,
        expression: a!localVariables(
          local!currentContractPeriodIndices: wherecontains(
            fv!item.costAdjustments.contractPeriodNum,
            tointeger(local!uniqueContractPeriods.period)
          ),
          
          /* we can also move this into a local variable so we don't have to do the same indexing over and over again */
          local!currentUniqueContractPeriod: index(
            local!uniqueContractPeriods,
            local!currentContractPeriodIndices,
            null
          ),
          
          if(
            a!isNullOrEmpty(local!currentContractPeriodIndices),
            fv!item,
            
            a!update(
              fv!item,
              "costAdjustments",
              
              a!update(
                a!update(
                  a!update(
                    a!update(
                      fv!item.costAdjustments,
                      "lengthOfMonth",
                      tointeger(local!currentUniqueContractPeriod.noOfMonths)
                    ),
                    "periodStartDate",
                    local!currentUniqueContractPeriod.startDate
                  ),
                  "periodEndDate",
                  local!currentUniqueContractPeriod.endDate
                ),
                "LengthOfDay",
                local!currentUniqueContractPeriod.noOfDays
              )
            )
          )
        )
      )
    )

    Second: do I see a 5-layer-deep a!update()?!?!?  What is the reasoning there?  a!update handles multiple named properties inherently.

    It looks like the top one is inserting a new property called "costAdjustments" but that the subsequent 4 are all adjusting properties under the "costAdjustments" layer, so we'll combine those 4 into one:

    a!save(
      local!newEstimates2,
      
      a!forEach(
        items: local!newEstimates,
        expression: a!localVariables(
          local!currentContractPeriodIndices: wherecontains(
            fv!item.costAdjustments.contractPeriodNum,
            tointeger(local!uniqueContractPeriods.period)
          ),
          local!currentUniqueContractPeriod: index(
            local!uniqueContractPeriods,
            local!currentContractPeriodIndices,
            null
          ),
          
          if(
            a!isNullOrEmpty(local!currentContractPeriodIndices),
            fv!item,
            
            a!update(
              fv!item,
              "costAdjustments",
              
              /* the 4-layer nesting can be replaced by a single a!update: */
              a!update(
                data: fv!item.costAdjustments,
                index: {"lengthOfMonth", "periodStartDate", "periodEndDate", "LengthOfDay"},
                value: {
                  tointeger(local!currentUniqueContractPeriod.noOfMonths),
                  local!currentUniqueContractPeriod.startDate,
                  local!currentUniqueContractPeriod.endDate,
                  local!currentUniqueContractPeriod.noOfDays
                }
              )
              
              /*a!update(
                a!update(
                  a!update(
                    a!update(
                      fv!item.costAdjustments,
                      "lengthOfMonth",
                      tointeger(local!currentUniqueContractPeriod.noOfMonths)
                    ),
                    "periodStartDate",
                    local!currentUniqueContractPeriod.startDate
                  ),
                  "periodEndDate",
                  local!currentUniqueContractPeriod.endDate
                ),
                "LengthOfDay",
                local!currentUniqueContractPeriod.noOfDays
              )*/
            )
          )
        )
      )
    )

    ...essentially reducing 67 lines of code to just shy of 40 (after commented rows are removed and discounting some of the padding blank-lines i added back for increased eye comfort).

    Beyond this, it's down to the inherent shape of your data, whether or not the indexing is functioning as you expect (i can't tell since there's no sample data in place and no real context to go on).  If you want to post an all-inclusive sample (a rule where all data is declared in local variables, external rules aren't used, and data is parsed in local variable definitions instead of a!save() statements), then i can take a look at that and help figure out why something might still not be working as expected.

  • Mike,  I was working towards the same solution when you sent this.  Yours looks a lot cleaner, it is compiling cleanly, but when I hit that section of the code it errors out.

    Error in a!forEach() expression during iteration 1: Expression evaluation error at function a!update [line 262]: Invalid index: cannot index multiple indices of type List of Text String into type UMS_Contract_Details?list

    I have been flagged by the system as someone that posts spam.  I am not sure if it is because I posted two identical posts.  It was not my intention to do so.  Or it could be the fact that I posted my page data and it is a lot of data.  Either way, I want to keep getting help.  I just want to avoid more system notifications.

    Do you want me to post the local variable data for newEstimates and uniqueContractPeriods?

Reply
  • Mike,  I was working towards the same solution when you sent this.  Yours looks a lot cleaner, it is compiling cleanly, but when I hit that section of the code it errors out.

    Error in a!forEach() expression during iteration 1: Expression evaluation error at function a!update [line 262]: Invalid index: cannot index multiple indices of type List of Text String into type UMS_Contract_Details?list

    I have been flagged by the system as someone that posts spam.  I am not sure if it is because I posted two identical posts.  It was not my intention to do so.  Or it could be the fact that I posted my page data and it is a lot of data.  Either way, I want to keep getting help.  I just want to avoid more system notifications.

    Do you want me to post the local variable data for newEstimates and uniqueContractPeriods?

Children
  • I have been flagged by the system as someone that posts spam. 

    This is happening to a lot of us busier posters, almost constantly lately - the spam detection software they use is way too sensitive (but if they lower the sensitivity, spambots almost instantly overwhelm the community with junk posts).  Just file the appeal and your posts should be un-spammed soon (if not i'll send the URL to some friends behind the scenes and they can manually do stuff).

    Your error message sorta depends on the shape of your overall data.  One thing you need to remember though is, the function "whereContains()" returns an Array data type, even if it matches only one item (like even if you're searching a list for a unique value). Thus if you expect it to return a SINGLE result, and you're using that result in a manner that's sensitive to multiples, you will need to force it down to a single value.  My normal trick for that is to wrap it in an extra index() call, like

    local!currentContractPeriodIndices: index(
      wherecontains(
        fv!item.costAdjustments.contractPeriodNum,
        tointeger(local!uniqueContractPeriods.period)
      ),
      1,
      null()
    ),
    ,
    which should return a plain (single typed) index if the whereContains finds the valid item, and NULL otherwise.

  • I am sure there could be some refinement of the code.  But, I got it to work.  Thank you for the help.

    a!save(  
                  target: local!newEstimates,  
                  value: a!forEach(  
                    /*Loops on newEstimates*/
                    items: local!newEstimates,  
                    expression: a!localVariables(  
                      local!updatedCostAdjustments: a!forEach(
                        /*Loops on only the cost adjustments*/
                        items: fv!item.costAdjustments,  
                        expression: a!localVariables(  
                          /*Pulls the one match and doesnt search for it again till next loop.*/
                          local!whatPeriodNum: fv!item.contractPeriodNum,
                          local!matchingPeriod:reject(fn!isnull,
                          a!forEach(  
                            /*Loop and find matching period row*/
                            items: local!uniqueContractPeriods,  
                            expression: if(  
                              fv!item.period = local!whatPeriodNum,  
                              fv!item,  
                              null  
                            )  
                          )
                          ),
                          /*Do Single Update for Each Row*/
                          if(  
                            local!matchingPeriod.period = fv!item.contractPeriodNum,  
                            a!update(
                              data: fv!item,  
                              index: {  
                                "periodStartDate",  
                                "periodEndDate",  
                                "LengthOfMonth",  
                                "LengthOfDay",
                                "updatedDate",
                                "updatedBy"
                              },  
                              value: {  
                                local!matchingPeriod.startDate,  
                                local!matchingPeriod.endDate,  
                                local!matchingPeriod.noOfMonths,  
                                local!matchingPeriod.noOfDays,
                                todate(today()),
                                loggedInUser()
                              }  
                            ),  
                            fv!item  
                          )  
                        )  
                      ),
                      a!update(  
                        fv!item,  
                        "costAdjustments",  
                        local!updatedCostAdjustments  
                      )  
                    )  
                  )  
                )