Skip to content

OBPIH-6037 Add bin location data to expiration reports#4468

Merged
awalkowiak merged 4 commits intofeature/upgrade-to-grails-3.3.10from
OBPIH-6037-add-bin-location-data-to-expiration-reports
Feb 2, 2024
Merged

OBPIH-6037 Add bin location data to expiration reports#4468
awalkowiak merged 4 commits intofeature/upgrade-to-grails-3.3.10from
OBPIH-6037-add-bin-location-data-to-expiration-reports

Conversation

@drodzewicz
Copy link
Collaborator

No description provided.

…and without binlocation included

- refactor controller action for expiring stock to include binLocation if such parameter is passed
- refactor controller action for expired stock to include binLocation if such parameter is passed
- refactored listExpiredStock gsp to handle new data model
- refactored listExpiringStock gsp to handle new data model
@drodzewicz drodzewicz self-assigned this Jan 22, 2024
@drodzewicz drodzewicz changed the title Obpih 6037 add bin location data to expiration reports OBPIH-6037 Add bin location data to expiration reports Jan 22, 2024
data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
data = data.findAll { inventoryItems.contains(it.inventoryItem) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we do this filtering in the PA query?
I think we could just pass the inventoryItems (or inventoryItems.id) to getQuantityByBinLocation (or the second method as well) and just add the IN :inventoryItems clause?

This could additionally improve the performance.

And a suggestion (regarding what version you prefer):

List<Map> data = withBinLocation 
   ? productAvailabilityService.getQuantityOnHandByBinLocation(location) 
   : productAvailabilityService.getQuantityOnHandByInventoryItem(location)
         .collect{ key, val -> [ inventoryItem: key, quantity: val ] }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can do it.
I just didn't want to modify the old service methods for that but it probably is worth changing it.
I was following the old logic for getting the expired or expiring inventoryItems and filtering the PA by that but I agree, this should probably be done in the db.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to mention is that we should probably have this type of logic in a more central service, like ProductAvailabilityService. The DashboardService was created before our product availability and should probably be phased out or refactored (i.e. it should delegate to product availability service and other reporting services).

data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
data = data.findAll { inventoryItems.contains(it.inventoryItem) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be a repetition of what is done in the listExpiredStock. Maybe we could create a private method in this controller (as you know me - I'd prefer to have that type of logic in the service, but no must, since this probably needs to be refactored anyway, anytime soon).

Comment on lines +578 to +579
Location location = Location.get(session.warehouse.id)
Category categorySelected = Category.get(params.category)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we use data services since we are on Grails 3?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's come up with our conventions for data services before we go lock, stock, and barrel. But what exactly would be changing here?

Also, what is going to happen if params.category is null here?

And just for giggles, we could use Location, Category, Boolean as arguments to this action and be done with the data binding.

Copy link
Collaborator

@kchelstowski kchelstowski Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmiranda in general the difference is that data service's read operations are run in a readOnly transaction.
I once mentioned to the guys, that I needed to do a research about what happens if we are in a service (marked as @Transactional) and we'd run a method from data service that is readOnly - if the outer (service's) transaction would "swallow" that, hence the fetch would be done using our outer transaction, or if it would set our outer transaction as readOnly for a while.
In the end I think this readOnly doesn't matter at this point, because as soon as we run the:

categoryDataService.get(params.category)

the readOnly transaction would commit at this point (so in further lines we could still update a category).

Also, what is going to happen if params.category is null here?

The data service's get evaluates to the basic Domain.get anyway, hence it'd return null if an entity didn't exist.

I think the data services methods matter much, if we do not take care of a transaction lifecycle on our own, because in the end, they evaluate to basic domain methods like Domain.get(...), Domain.delete(...), Domain.save(...), so I don't have a strong opinion about forcing to use data services for such simple cases (if we do take care of transaction boundaries on our own).
I find the data services very helpful and powerful for stuff like fetch join query, etc. where it has helped us a lot already.

Comment on lines 586 to 591
if (withBinLocation) {
data = productAvailabilityService.getQuantityOnHandByBinLocation(location)
} else {
data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't it be just written using a ternary operator?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, I can.
I see you guys really hate if/else statements... 😅
I feel like simple if/else most of the time is much more readable than a ternary.
Ternary is great for very short statements and this case kind of applies to that, although I was also considering the case where in the future we want to add another parameter which would call another service method and ternary in this case would be very bad.

But yeah, I can change it to ternary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from my perspective it was only a suggestion for you to decide, which approach you prefer there. I want everyone to feel comfortable with their code style, since your version is not "breaking" any rules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No @kchelstowski that is not how this works. It's ternary or nothing!!!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just kidding. I don't feel strongly either way. I do feel strongly that the code should be refactored and probably moved to the service layer and that the getExpiredStock should probably handle all or most of the work. But we can save that for another day in case it'll be too much work.

Comment on lines +611 to +612
Location location = Location.get(session.warehouse.id)
Category category = Category.get(params.category)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as above

Comment on lines 621 to 626
if (withBinLocation) {
data = productAvailabilityService.getQuantityOnHandByBinLocation(location)
} else {
data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same as above

<g:checkBox id="${dataEntry.inventoryItem?.id }"
name="inventoryItem.id"
class="checkbox"
checked="${false }"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
checked="${false }"
checked="${false}"

&nbsp; <g:message code="default.button.downloadAsCSV.label" default="Download as CSV"/>
</g:link>

<g:link params="[format:'csv',category: params.category, withBinLocation: true]" controller="inventory" action="listExpiredStock" class="button">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<g:link params="[format:'csv',category: params.category, withBinLocation: true]" controller="inventory" action="listExpiredStock" class="button">
<g:link params="[format: 'csv',category: params.category, withBinLocation: true]" controller="inventory" action="listExpiredStock" class="button">

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ split into new lines

</div>

<g:link params="[format:'csv',category:params.category]" controller="${controllerName}" action="${actionName}" class="button">
<g:link params="[format:'csv',category: params.category]" controller="inventory" action="listExpiredStock" class="button">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are a lot of params, maybe it would be more readable if we split those into new lines?

Comment on lines +578 to +579
Location location = Location.get(session.warehouse.id)
Category categorySelected = Category.get(params.category)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's come up with our conventions for data services before we go lock, stock, and barrel. But what exactly would be changing here?

Also, what is going to happen if params.category is null here?

And just for giggles, we could use Location, Category, Boolean as arguments to this action and be done with the data binding.

Comment on lines 586 to 591
if (withBinLocation) {
data = productAvailabilityService.getQuantityOnHandByBinLocation(location)
} else {
data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No @kchelstowski that is not how this works. It's ternary or nothing!!!!

Comment on lines 586 to 591
if (withBinLocation) {
data = productAvailabilityService.getQuantityOnHandByBinLocation(location)
} else {
data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just kidding. I don't feel strongly either way. I do feel strongly that the code should be refactored and probably moved to the service layer and that the getExpiredStock should probably handle all or most of the work. But we can save that for another day in case it'll be too much work.

data = productAvailabilityService.getQuantityOnHandByInventoryItem(location)
.collect{ key, val -> [ inventoryItem: key, quantity: val ] }
}
data = data.findAll { inventoryItems.contains(it.inventoryItem) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to mention is that we should probably have this type of logic in a more central service, like ProductAvailabilityService. The DashboardService was created before our product availability and should probably be phased out or refactored (i.e. it should delegate to product availability service and other reporting services).

List<Category> categories = inventoryItems?.collect { it.product.category }?.unique()

List<Map> data
if (withBinLocation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't read the ticket, but is there still a case where we don't need or want the bin location data?

@drodzewicz drodzewicz force-pushed the OBPIH-6037-add-bin-location-data-to-expiration-reports branch from be54b3b to fbac56d Compare January 25, 2024 11:55
…ndByBinLocation and getQuantityOnHandByInventoryItem
@drodzewicz drodzewicz force-pushed the OBPIH-6037-add-bin-location-data-to-expiration-reports branch from fbac56d to bd4767c Compare January 25, 2024 17:04
groupProperty("inventoryItem", "inventoryItem")
groupProperty("binLocation", "binLocation")
sum("quantityOnHand", "quantityOnHand")
sum("quantityAvailableToPromise", "quantityAvailableToPromise")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you made sure this would eventually return 0? I'm asking because of this:

sum(case when pa.quantityAvailableToPromise > 0 then pa.quantityAvailableToPromise else 0 end)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a function that transforms this result collectQuantityOnHandByBinLocation handles the logic for setting a 0 for null values, but does not handle negative values...
Thanks for pointing this out, I think I am going to have to revert this to a simple SQL because it is quite hard to implement this logic with CASE WHEN in the criteria

quantityMap[it[0]] = it[1]
Map<InventoryItem, Integer> getQuantityOnHandByInventoryItem(Location location, List<InventoryItem> inventoryItems = []) {
if (!location) {
throw new RuntimeException("Must specify location in order to get quantity on hand")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was there a reason you refactored this part to throw an exception? Maybe there is a case where we'd still want to proceed with e.g. refresh PA, and by doing so, you'd break that operation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I have checked it seems we always require a location, but to be on the safe side I think I will revert it to returning an empty array instead of throwing an Exception.

quantityMap[it[0]] = it[1]
Map<InventoryItem, Integer> getQuantityOnHandByInventoryItem(Location location, List<InventoryItem> inventoryItems = []) {
if (!location) {
return []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method by default was returning an empty Map, not a List - even the returned type suggests it should be the Map.
I think this would not work as expected with the list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, there was a similar case that returns a list so I messed it up.

- return empty lists instead of throwing an Exception when location is not provided
- bring back previous SQL query for QOHByBinLocation
@drodzewicz drodzewicz force-pushed the OBPIH-6037-add-bin-location-data-to-expiration-reports branch from eaa29ba to 3474233 Compare January 26, 2024 15:20
@awalkowiak awalkowiak merged commit 9034da3 into feature/upgrade-to-grails-3.3.10 Feb 2, 2024
@awalkowiak awalkowiak deleted the OBPIH-6037-add-bin-location-data-to-expiration-reports branch February 2, 2024 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants