-
-
Notifications
You must be signed in to change notification settings - Fork 400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OBPIH-5616 Improve calculating product availability for picklist items #4005
Conversation
grails-app/services/org/pih/warehouse/inventory/ProductAvailabilityService.groovy
Outdated
Show resolved
Hide resolved
@@ -1100,7 +1102,7 @@ class StockMovementService { | |||
// Save requisition item before PA refresh | |||
requisitionItem.save(flush: true) | |||
|
|||
productAvailabilityService.refreshProductsAvailability(requisitionItem?.requisition?.origin?.id, [requisitionItem?.product?.id], false) | |||
productAvailabilityService.refreshProductsAvailability(requisitionItem?.requisition?.origin?.id, [requisitionItem?.product?.id], binLocations?.unique(), false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if the binLocations List shouldn't be a Set instead. That way it would eliminate the need for performing a binLocations?.unique()
, but I am unsure about the performance implications of the Set
vs List
. I see some people mentioning on stack overflow that HashSets are not as well optimised while other say that it is preferable in this kind of a situation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a fair assessment. The performance implications between set / list.unique will be negligible compared to the time spent doing anything with the database so it would probably be better to handle this properly with a set.
(converted back to draft, because there will be one more improvement included) |
} | ||
} | ||
|
||
productIds.each { String productId -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use a Product.getAll(productIds) here.
log.info "Refreshing product availability location ${location}, product ${product}, binLocations ${binLocations}, forceRefresh ${forceRefresh}..." | ||
def startTime = System.currentTimeMillis() | ||
List calculatedBinLocations = calculateBinLocations(location, product, binLocations) | ||
// FIXME: if this deadlocks, it safe to auto-retry here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not. Are you seeing a lot of deadlocks here? If so, we should investigate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmiranda this was just copy paste from similar method above
FROM location_supported_activities lsa_select | ||
GROUP BY lsa_select.location_id | ||
) lsa ON lsa.location_id = l.id | ||
WHERE (r.origin_id = :locationId AND r.status IN (${RequisitionStatus.listPending().collect { "'$it'" }.join(',')})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't you pass this in as a query parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmiranda Because it was not handled (parsed to string) properly for sql to work as expected
|
||
return results.collect { [binLocation: it[0]?.id, inventoryItem: it[1]?.id, quantityAllocated: it[2]] } | ||
def query = """ | ||
SELECT bin_location_id, inventory_item_id, SUM(quantity) FROM ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned on the call the other day, we should include product and location in the select even though they are passed in as query parameters. There's a small chance that might lead to slower performance if those columns aren't in a covering index but I don't suspect that's the case. Also, the inner (raw query) should not include the sum / group by if we can help it. It's usually better to leave the group by until the end i.e. outer query. I could be wrong about this, but it feels wrong to apply aggregation inside a nested query if you're going to do the same on the outside.
Also I see we're getting rid of the weird count + count / count(*). I think that's safe since we're simplifying the query by breaking into two separate queries and unioning the results. But we should try to understand and/or compare results from the previous query so we know we're getting the same results.
sum(pli.quantity) as quantity | ||
FROM picklist_item pli | ||
INNER JOIN picklist p ON pli.picklist_id = p.id | ||
LEFT JOIN requisition_item ri ON pli.requisition_item_id = ri.id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, we don't need to use LEFT JOIN any longer. That was one of the reasons why I wanted to do a UNION here. I think location (bin location) is the only table that needs to be LEFT JOIN'd.
SELECT | ||
GROUP_CONCAT(lsa_select.supported_activities_string) as activities, | ||
lsa_select.location_id as location_id | ||
FROM location_supported_activities lsa_select |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember we want to check both location and location type. Location supported overrides location type supported activities. If that's too difficult, then we can save that for another time.
GROUP BY bin_location_id, inventory_item_id; | ||
""" | ||
|
||
def results = dataService.executeQuery( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can't figure out the issue where, try using Hibernate to execute the query instead of the Groovy SQL client we're using.
@@ -1100,7 +1102,7 @@ class StockMovementService { | |||
// Save requisition item before PA refresh | |||
requisitionItem.save(flush: true) | |||
|
|||
productAvailabilityService.refreshProductsAvailability(requisitionItem?.requisition?.origin?.id, [requisitionItem?.product?.id], false) | |||
productAvailabilityService.refreshProductsAvailability(requisitionItem?.requisition?.origin?.id, [requisitionItem?.product?.id], binLocations?.unique(), false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a fair assessment. The performance implications between set / list.unique will be negligible compared to the time spent doing anything with the database so it would probably be better to handle this properly with a set.
@@ -2117,13 +2120,14 @@ class StockMovementService { | |||
picklistItem.disableRefresh = Boolean.TRUE | |||
picklistItem.picklist?.removeFromPicklistItems(picklistItem) | |||
picklistItem.requisitionItem?.removeFromPicklistItems(picklistItem) | |||
binLocations.add(picklistItems?.binLocation?.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be picklistItem?.binLocation?.id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was causing the following stacktrace
2023-05-11 15:03:36,185 [http-8080-4] INFO filters.AccessLogFilters - stockMovementApi.reviseItems: [user:jmiranda, location:Distribution Center]
2023-05-11 15:03:36,193 [http-8080-4] INFO filters.AccessLogFilters - No rule for stockMovementApi:reviseItems -> allow anonymous
2023-05-11 15:03:36,198 [http-8080-4] INFO core.UserService - Is role ROLE_FINANCE in [ROLE_MANAGER, ROLE_AUTHENTICATED, ROLE_BROWSER, ROLE_ADMIN, ROLE_SUPERUSER, ROLE_ASSISTANT] = false
2023-05-11 15:03:36,198 [http-8080-4] INFO core.UserService - Is role ROLE_SUPERUSER in [ROLE_MANAGER, ROLE_AUTHENTICATED, ROLE_BROWSER, ROLE_ADMIN, ROLE_SUPERUSER, ROLE_ASSISTANT] = true
2023-05-11 15:03:36,266 [http-8080-4] INFO inventory.StockMovementService - Shipment workflow Stock movement shipment workflow
2023-05-11 15:03:36,385 [http-8080-4] INFO api.StockMovementApiController - binding lineItems: [[id:ff808181880c4df001880c53118d0003, reasonCode:LOW_STOCK, quantityRevised:1]]
2023-05-11 15:03:36,399 [http-8080-4] INFO inventory.StockMovementService - Removing previous changes, picklists and shipments, if present
2023-05-11 15:03:36,527 [http-8080-4] INFO inventory.ProductAvailabilityService - refreshProductsAvailability: 8a8a9e9665c4f59d0165c54ec6b10027, [10001], [[8a8a9e96687c94ce0168bab1c5a8285c]], false
2023-05-11 15:03:36,532 [pool-2-thread-1] INFO inventory.RefreshProductAvailabilityEventService - Application event been disabled by event publisher
2023-05-11 15:03:36,532 [pool-2-thread-1] INFO inventory.RefreshProductAvailabilityEventService - Application event [disableRefresh:true, forceRefresh:false, class:class org.pih.warehouse.inventory.RefreshProductAvailabilityEvent, locationId:8a8a9e9665c4f59d0165c54ec6b10027, productIds:[10001], synchronousRequired:false, timestamp:1683835416503, metaClass:org.codehaus.groovy.runtime.HandleMetaClass@4ba20442[groovy.lang.ExpandoMetaClass@4ba20442[class org.pih.warehouse.inventory.RefreshProductAvailabilityEvent]], source:org.pih.warehouse.picklist.PicklistItem : ff808181880c4df001880c59fde2000d]
2023-05-11 15:03:36,549 [http-8080-4] ERROR errors.GrailsExceptionResolver - Exception occurred when processing request: [POST] /openboxes/api/stockMovements/ff808181880c4df001880c528d670001/reviseItems
Stacktrace follows:
org.hibernate.TypeMismatchException: Provided id of the wrong type for class org.pih.warehouse.core.Location. Expected: class java.lang.String, got class java.util.ArrayList
at org.pih.warehouse.inventory.ProductAvailabilityService$_refreshProductsAvailability_closure3.doCall(ProductAvailabilityService.groovy:106)
at org.pih.warehouse.inventory.ProductAvailabilityService.refreshProductsAvailability(ProductAvailabilityService.groovy:104)
at org.pih.warehouse.inventory.ProductAvailabilityService$$FastClassByCGLIB$$7ee8fd31.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
at org.pih.warehouse.inventory.ProductAvailabilityService$$EnhancerByCGLIB$$80269ed0.refreshProductsAvailability(<generated>)
at org.pih.warehouse.inventory.ProductAvailabilityService$refreshProductsAvailability.call(Unknown Source)
at org.pih.warehouse.inventory.StockMovementService.removeShipmentAndPicklistItemsForModifiedRequisitionItem(StockMovementService.groovy:2130)
at org.pih.warehouse.inventory.StockMovementService$_reviseItems_closure65.doCall(StockMovementService.groovy:1944)
at org.pih.warehouse.inventory.StockMovementService.reviseItems(StockMovementService.groovy:1934)
at org.pih.warehouse.inventory.StockMovementService$$FastClassByCGLIB$$b65c0b56.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
at org.pih.warehouse.inventory.StockMovementService$$EnhancerByCGLIB$$456057a7.reviseItems(<generated>)
at org.pih.warehouse.inventory.StockMovementService$reviseItems.call(Unknown Source)
at org.pih.warehouse.api.StockMovementApiController$_closure12.doCall(StockMovementApiController.groovy:265)
at org.pih.warehouse.api.StockMovementApiController$_closure12.doCall(StockMovementApiController.groovy)
at grails.plugin.springcache.web.GrailsFragmentCachingFilter.doFilter(GrailsFragmentCachingFilter.groovy:66)
at net.sf.ehcache.constructs.web.filter.Filter.doFilter(Filter.java:86)
at org.grails.plugin.resource.DevModeSanityFilter.doFilter(DevModeSanityFilter.groovy:44)
at java.lang.Thread.run(Thread.java:748)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmiranda yes this should be picklistItem?.binLocation?.id
(singular item)
@@ -74,12 +75,13 @@ class PicklistService { | |||
it.disableRefresh = Boolean.TRUE | |||
picklist.removeFromPicklistItems(it) | |||
orderItem.removeFromPicklistItems(it) | |||
binLocations.add(it.binLocation?.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're not doing any filtering (like in cases above) we could probably just collect all of the bin locations before the findAll.
def binLocationIds = picklist?.picklistItems?.collect { it?.binLocation?.id }
…vailability refresh
…native SQL query API
@awalkowiak Please review when you get a chance. |
.setString("locationId", location?.id) | ||
.setString("productId", product?.id ?: '') | ||
.setParameterList("pendingRequisitionStatuses", RequisitionStatus.listPending().collect { it.name() }) | ||
.setParameterList("pendingOrderStatuses", OrderStatus.listPending().collect { it.name() }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just wondering if it would work without .collect
here, since toString()
returns name()
, if Hibernate would convert that properly
@jmiranda the code looks good, but I still want to test it out locally. Did you omit the location type activity codes check on purpose or you forgot about this? (I am not 100% sure if this is required) |
@jmiranda LGTM. I checked the "problematic" (refreshing product availability after generating picklist) part locally. It looks good. I cannot give the approval, since I created the PR, but I think it can be merged. |
No description provided.