Inventory Adjustments data location

Hello,

For Inventory ‘adjustments’ (e.g. if we need to increment/decrement inventory after a physical count of inventory), are those kept in the Movements object or somewhere else? And they are a Movement, which MovementType(s) should I be looking for?

I believe in Quantify this is where the database gets ‘locked’ (at least to the user in the UI), I’m assuming so that multiple users are not trying to adjust inventory at the same time, and/or so inventory quantities do not get adjusted while someone has a transaction open.

Let me know if you need more info on this one.
Thanks,
Alex

Hello,

Just wanted to check in on this - is it possible to get a response for this question in the next day or two?

I know the developer team is very busy with the latest release coming out; just want to plan accordingly on our end since we have a tight deadline coming up as well and I need to know where this information is.

I will keep looking in the meantime and update this thread if I figure it out myself.

Thanks so much,
Alex

I believe I found it - StockedProductAdjustment. A couple questions regarding this, however:

It appears that this is not a true ‘log’ table, that creates a new record for each adjustment made. By my eyes it looks like the old record is overwritten with the new adjustment, if one has been made. Is that indeed the case?

Further, it appears there is no versioning (via VersionStamp, CreatedDate, etc.) on the StockedProductAdjustment object. Is this also indeed the case?

If yes to both the above, what is the recommended approach for integrating new adjustments over to an accounting system? It seems as though I would need to compare all fields coming in with a database I keep offline.

It may be best to simply add this object to be VersionStamp-ed, as we did recently with Products and Orders, but I am not sure if that is possible.

Please let me know if you have any questions.

Thank you,
Alex

Hi Alex.

Adjustments performed in Quantify UI using ‘Adjust quantities for the selected location’ is logged and available in LogEntryList, which may be fetched by any of the following options as needed. Each entry has a LogDate property -

LogEntryList.GetLogEntryList(LogEntry.ChildTypes.StockedProduct)

LogEntryList.GetLogEntryList(Stocked-Product-ID)

However, if you’d like to track changes using a VersionStamp instead, which currently is unavailable in StockedProduct, I shall put it as request to the team.

Thanks for posting.

Understood - thank you for that info.

Looking through the LogEntryList object, I am still confused. Here is my approach:

I obtain a collection of all StockedProductAdjustments where the ProductType is ‘Product’. I know that it populates the collection with several hundred records. My intent is to loop through this, retrieve the LogEntryList, see which have changed since our process last ran, and run them through the integration to our accounting system.

However, when I attempt to loop through and get the LogEntryList for each StockedProductID (as you indicated above), I never get any results. The LogEntryList list object instance is always empty.

Can you please clarify exactly how I am supposed to do this? Simply retrieving all records by using:
LogEntryList.GetLogEntryList(LogEntry.ChildTypes.StockedProduct) does not work (unless I am missing something), because there is no ‘ID’ field in the LogEntryList entries to link out to the StockedProduct in question.

Here is my code snippet:

//***** Loop through all Adjustments *****
StockedProductAdjustmentCollection all_adjustments = StockedProductAdjustmentCollection.GetStockedProductAdjustmentCollection(ProductType.Product);
foreach (StockedProductAdjustment myAdjustment in all_adjustments)
{
//***** Need to use log entry list object for versioning of adjustments *****
LogEntryList logAdjustments = LogEntryList.GetLogEntryList(myAdjustment.StockedProductID);
if (logAdjustments.Count > 0)
{
//Do something
}
}

Thanks for your continued assistance with this,
Alex

To answer your question as well, I would like to see VersionStamp added for StockedProducts as well, to maintain consistency in our code. It would be necessary if the LogEntryList object isn’t able to work for my scenario.

If you need me to provide more reasons why VersionStamp would help us in order to get it added, let me know and I can provide them.

Hi Alex.

StockedProductAdjustmentCollection retrieves product data as expected by specific UI components, you may be able to give it miss for the purpose of checking changes.

An adjustment performed in Quantify UI using ‘Adjust quantities for the selected location’ when successfully completed should result in StockedProduct data being modified and adding entry in LogEntryList

Changes/updates to products for a location may be checked for as below…

            bool stockedProductUpdatedAtLocation = false;
            LogEntryList logs = LogEntryList.GetLogEntryList(STOCKING_LOCATION_ID);
            if (logs.Count(log => log.LogDate >= DateTime.Now.Date) > 0)
                stockedProductUpdatedAtLocation = true;
            Debug.WriteLine(stockedProductUpdatedAtLocation);

However, the above does not get change-info for each part/product at the location; this will be available through VersionStampList in the upcoming release.

Hello,

Got it, thank you very much for the clarification and quick response.

Is it possible to get that change added into our hot fix version of Quantify that you guys were able to get us a few days ago? (see this thread for more details: https://quantifyapi.avontus.com/t/purchase-order-modified-version-stamp-field/492/21)

This is another one of those data entities where we need that versioned information in order to be able to integrate with our accounting system - there isn’t really a workaround.

Alternatively, if the upcoming release will be within the next week, then we can probably wait for the full release.

Let me know what you think when you have a chance.
Thanks so much,
Alex

Hi Alex.

We shall post today’s EOD build here, which includes the VersionStamp on StockedProduct.

Thanks for posting.

Please find the URL to pre-QA build below -

Quantifyx64.exe (90.4 MB)

It may be required to login once to apply secured changes. Also, for the API applications, it may be required to refresh the Library DLL

Hello,

Thank you so much for the very quick turnaround for this new code. This update resolved the InvoiceCollection.GetInvoiceCollection error I ran into, however I am unable to find the VersionStamp field on StockedProducts. Can you please provide me some guidance on exactly where this should be?

I looked on the following objects, and the VersionStamp field is not there:
StockedProduct
StockedProductList
StockedProductListItem
StockedProductAdjustment

Am I missing something obvious? Also, I did notice that the Avontus.Core DLL did not change when we ran our update - is this expected or could that be the culprit?

The versions for each of the DLLs that we have are as follows:
Avontus.Core - 3.7.0.1
Avontus.Quantify.Reports - 8.3.1460.125
Avontus.Rental.Library - 8.3.1460.125
Avontus.Rental.Utility - 8.3.1460.125

Let me know if you need more info from me on this, and thanks again so much for your help.

Alex

Hi Alex.

Currently this is included in the dedicated VersionStampList object as below, you can expect it to be added to StockedProductListItem in the future releases.

VersionStampList stockedProductVersions = VersionStampList.GetVersionList(VersionStampList.DataObjectName.StockedProduct);
foreach (var item in stockedProductVersions)
{
   Debug.WriteLine($"StockedProductID: {item.Key} Version: {item.Value}");
}

Thanks for posting.

Ah, I see - that makes sense. I should be able to make that work in that case.

Hopefully final question then on this - it looks like I am not using the necessary reference yet to access the VersionStampList object. Where is this object located?

I am using the following references as of now:
using Avontus.Core;
using Avontus.Rental.Library;
using Avontus.Rental.Library.Accounting;
using Avontus.Rental.Library.Accounting.XeroAccounting;
using Avontus.Rental.Library.Security;
using Avontus.Rental.Library.ToolWatchImport;
using Avontus.Rental.Library.Logging;
using Avontus.Rental.Library.Database;

Thanks so much,
Alex

Hi Alex.

Please note that VersionStampList class is under Avontus.Rental.Library.Utility namespace.

using Avontus.Rental.Library.Utility;

The VersionStampList utility class takes VersionStampList.DataObjectName as parameter, which is synonymous to the table in database, and lists (Primary-Key-ID, VersionStamp) for the entire table in an efficient manner.

Thanks for posting.

Hello,

Excellent, thank you for the info on where that was. I was able to access it and write my code accordingly to look at the VersionStamps of the StockedProducts.

I have one question regarding the data in the StockedProduct object, however - I ran my code in debug mode and noticed that after an adjustment is made (using ‘adjust quantities’ for a location), the ChangeQuantity field never gets populated, and further the ‘QtyForRentOriginal’ and ‘QuantityForRent’ fields are both equal to the new quantity value, after the change was made (this is the same for the ‘QtyNewOriginal’ and ‘QuantityNew’, if referring to New quantities vs. Available quantities).

Seeing this I am confused how I would go about identifying how much the quantity actually changed. Is this supposed to be going into the ‘ChangeQuantity’ field, or is the original value supposed to be going into the Qty…Original field? Or is this just not how it works, and I am going about this incorrectly?

Thanks again for your continued assistance with this.
Alex

Additionally, I need to be able to identify if a StockedProduct’s quantity has been changed due to something other than the ‘Adjust quantities’ functionality (e.g. from a Movement).

Is the only way to do this by looking at the fields in the StockedProduct object such as ‘QuantityOnOrder’, ‘QuantityOnOrderNew’, etc. to see if they have a value, and if they do not have a value, then the StockedProduct’s quantity was adjusted via ‘Adjust quantities’?

Thanks,
Alex

Hi Alex.

Stocked product objects and tables are designed only to hold the current value for quantities. “Original” properties are meant for only for holding original values, in case of an edit, till the time it is saved to database. Only the current values are accessible using stocked product objects as below -

            StockedProductList stockedProductsBranch = StockedProductList.GetSerializedStockedProductList(LOC_ID, Guid.Empty);
            foreach (var item in stockedProductsBranch)
            {
                Console.WriteLine($"For Rent - {item.QuantityForRent} In Transit - {item.QuantityInTransit} New - {item.QuantityNew} Discrepancy - {item.QuantityReserved}");
            }

The history of changes, including the previous values, are not directly accessible from stocked product or related objects. A workaround, inside of Quantify, would be to obtain it from log entries as below. Since LogEntryListItem caters to textual logs the Product-ID is not exposed in the object, but the ID is still captured in the database. If it is useful I can place a request to expose Product-ID in the log items.

    LogEntryList logs = LogEntryList.GetLogEntryList(LOC_ID);
    foreach (var item in logs.Where(log => log.LogDate >= DateTime.Today).OrderByDescending(log => log.LogDate))
    {
        Console.WriteLine($"Change    - {item.Name} \r\nPart      - {item.ChildDescription} \r\nOld Value - {item.OldValue} \r\nNew Value - {item.NewValue} \r\n\r\n");
    }

Thanks for posting.

Thank you for getting back to me on this quickly.

This is interesting - but I think I understand the requirements.

I have one question and one comment to identify if this will be able to work for us:

  1. For the ‘item.Name’ field, I see options of ‘New Part Changed’ and ‘For Rent Part Changed’. Are these two options encapsulating for what I need to do, i.e. identify all instances where part quantities were adjusted using ‘Adjust products’? If not, can you please send a list of all the item.Name options I should be checking for in my code?

  2. I think the Product ID being included in the log items for these types of LogEntryList records is necessary to ensure this works, since we will need to identify which LogEntryList item corresponds to which StockedProductAdjustment object. To clarify on that as well - that would correspond to the ‘StockedProduct.BaseProductID’ field, correct?

Thanks again for the assistance, I think we are very close to a potential solution with this.

Alex

Hi Alex.

The log entry names, in this context, takes the following values depending on the quantity type that was adjusted, this is a logging feature that might provide a workaround, if it proves inadequate it may become necessary to save old quantities outside of Quantify -

  • For Rent Part Changed
  • Reserved Part Changed
  • Discrepancy Part Changed
  • In Transit Part Changed
  • New Part Changed
  • For Rent Part Added
  • Reserved Part Added
  • Discrepancy Part Added
  • In Transit Part Added
  • New Part Added

correspond to the ‘StockedProduct.BaseProductID’ field, correct?

That is correct, however, in the above example I’ve used StockedProductList instead of StockedProductAdjustment as latter is primarily designed as a command/action class for adjustments.

Thanks for posting.

Hello,

Sounds good, thank you for providing that list.

More questions now, as I am getting into debugging through the API and learning the intricacies of LogEntryList.

  1. When I look in the SQL LogEntryList table, there is an option for item.Name called ‘Available Part Changed’. Is this analogous to For Rent Part Changed? I am confused on the distinction.

For my second question, some background first:

I was able to identify my exact records that came from my adjustments via SQL by looking only for ‘New Part Changed’ and ‘Available Part Changed’ for the Name field in the LogEntryList table. It appears this Name is used if and only if the records came from an Adjustment via ‘Adjust quantities’, which is excellent news if I am right.

I set out to use the API to obtain those records that correspond to the adjustments via the following code:

LogEntryList myStockedProductLogs = LogEntryList.GetLogEntryList(LogEntry.ChildTypes.StockedProduct);

if (myStockedProductLogs.Any(item => item.Name == “New Part Changed” && item.ChildDescription.Split(separatorString, StringSplitOptions.RemoveEmptyEntries)[0] == myAdjustment.PartNumber))
{
var myAdjustmentLog = myStockedProductLogs.First(item => item.Name == “New Part Changed” && item.ChildDescription.Split(separatorString, StringSplitOptions.RemoveEmptyEntries)[0] == myAdjustment.PartNumber);
var changedQuantity = Int32.Parse(myAdjustmentLog.NewValue) - Int32.Parse(myAdjustmentLog.OldValue);
myTransLine.quantity = changedQuantity.ToString();
}
else
{
continue;
}

This fortunately returns my expected records, although it is a bit clunky with the string splitting going on to get at the Part Number. Nevertheless, it works consistently, unless I change the criteria to look for “Available Part Changed”. Then, it crashes with an “Index out of Bounds of Array” error, even though I expect to find records for that criteria, since it is clearly in the SQL table for those Parts (and even if I don’t find any, my List.Any() method shouldn’t crash, it should simply return no values).

So, my question is -

  1. Am I going about searching for adjustments of Available part quantities the right way with the code above (just replace “New” with “Available” in the criteria)? Do you have any advice on why it may be crashing?

In the meantime I will see if it is somehow a bug in my code.

Thanks so much, this time we are very close.

Alex