-
-
Notifications
You must be signed in to change notification settings - Fork 392
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-5803 Add submit for approval functionality to create request workflow #4288
Conversation
…lizationException in a published event
…ovement status, not shipment status
grails-app/services/org/pih/warehouse/inventory/RequisitionStatusTransitionEventService.groovy
Outdated
Show resolved
Hide resolved
…l activity and remove confirm submit modal
…usTransitionEvent
@@ -121,6 +121,7 @@ class Requisition implements Comparable<Requisition>, Serializable { | |||
|
|||
// Request approval fields | |||
Person approvedBy | |||
Person rejectedBy |
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 one of the tech huddles/standups - I forgot to add this field in the domain changes ticket, so I've added it now.
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 know that @drodzewicz also added the migration for rejectedBy within the ticket that he is working on, so we have to remember to avoid duplicating them
@@ -150,7 +151,8 @@ class Requisition implements Comparable<Requisition>, Serializable { | |||
statusSortOrder formula: RequisitionStatus.getStatusSortOrderFormula() | |||
monthRequested formula: "date_format(date_requested, '%M %Y')" | |||
comments joinTable: [name: "requisition_comment", key: "requisition_id"] | |||
events joinTable: [name: "requisition_event", key: "requisition_id"] | |||
// Fetch join in the events should be removed in Grails 3 and replaced with proper join query using data services | |||
events joinTable: [name: "requisition_event", key: "requisition_id"], fetch: '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.
I was getting a LazyInitializationException
when accessing the events in the RequisitionStatusTransitionEvent
- it is because the event is done in a different session (after the session where we fetch the Requisition ends).
Current solution actually adds an eager fetching of the events, but using the left join
clause, not calling a second query.
Normally I would not do it and would not recommend doing an eager fetch "globally", but I decided that it was not worth to spend time to create a custom query for that, because in Grails 1 we don't have data services.
This is why I left a comment to refactor this ASAP in Grails 3, to use a proper joining query using data services
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.
Every time I see an eager join I can't decide if we should go with it or do something else.
For me it might be fine but I hesitate to give a final opinion on this.
Just like you said in other comments, we will probably use @Service
methods for these scenarios in grails 3, so I am thinking maybe this should be a service method instead of a domain eager fetch
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.
@drodzewicz this would mean we’d have to write a pure SQL in order to fetch that.
I just thought it’s waste of time for now, since we’ll migrate in a moment and shouldn’t experience any performance issues during that time
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 not sure about that one, @jmiranda what's your opinion 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.
I would probably try a withNewSession in the event handler.
Requisition.withNewSession {
requisition = Requisition.read() // or Requisition.get() if you need to set something
}
Or if that doesn't work, just do a Requisition.read(id) or get(id) in the event handler.
Lastly, you could potentially use refresh the event handler but that one always scares me.
requisition.refresh()
Not sure if you need the withSession or withNewSession wrapper so I'll leave that as an exercise for the reader.
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.
of course get
/read
will work, but I wanted to avoid additional query to refetch it, but if you are fine with it, I can just fetch it again in the event service.
@@ -3440,6 +3447,9 @@ react.stockMovement.status.picking.description.label=The inventory selection nee | |||
react.stockMovement.status.picked.description.label=This shipment is ready to be shipped. The packing information may need to be entered. | |||
react.stockMovement.status.issued.description.label=This shipment has been shipped out. | |||
react.stockMovement.status.checking.description.label=This shipment is ready to be shipped. | |||
react.stockMovement.status.pending_approval.description.label=Waiting for approval | |||
react.stockMovement.status.approved.description.label=Approved | |||
react.stockMovement.status.rejected.description.label=Rejected |
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.
those are for the tooltips on list pages
@@ -230,6 +231,7 @@ class StockMovementService { | |||
void updateRequisitionStatus(String id, RequisitionStatus status) { | |||
|
|||
log.info "Update status ${id} " + status | |||
// TODO: In Grails the get below should be replaced by the data service get that joins the Events |
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.
here it sohuld be replaced by the data service query, something like getWithEvents
that would @Join('events')
I know that @drodzewicz has run into the same issue but with approvers, so most probably we'll have to write the query using @Query
and join those two tables (if I forget - remember to take care of left join there)
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 could (but don't have to) add a getWithEvents() to the Requisition class now.
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.
yeah, but since those data service annotations are not available in Grails 1, I would anyway have to write this HQL/SQL query by myself using .executeQuery
, wouldn't I?
requisition.save() | ||
return | ||
} | ||
Event event = createEvent(EventCode.SUBMITTED, requisition.origin, new Date()) |
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 not sure if SUBMITTED
is a correct code here, so ready for discussion/suggestions.
(It is if we submit a request "in an old way", so if we don't support the request approval workflow)
@@ -20,12 +21,18 @@ package org.pih.warehouse.core | |||
*{eventDate: 5/5/2010, eventLocation: Customs, eventType: ARRIVED}*/ | |||
class Event implements Comparable, Serializable { | |||
|
|||
def beforeInsert = { |
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 remember that we were changing this from closures to methods. I am not sure if the methods work in Grails 1, but if they work, I think it would be better to change them to avoid inconsistency in Grails 3.
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 is outdated - I had to remove this approach, since triggering an event creates its own session, hence the currentUser was null
Boolean isApprovalRequired() { | ||
return supportedActivities?.contains(ActivityCode.APPROVE_REQUEST.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.
I can see that there is a method called supports
maybe it would be better to use it?
I mean:
Boolean isApprovalRequired() {
return supports(ActivityCode.APPROVE_REQUEST.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.
Yeah, thanks for pointing that out, I forgot to ask about it as well - if we should use contains like I did, or supports, because supports also checks for locationType’s supportedActivities and I was not sure whether the request approval is supposed to work if we add this activity to a locationType
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 it should use supports
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.
Agreed
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"> | ||
|
||
<changeSet author="kchelstowski" id="210920231330-1"> |
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.
Just a question, why are you numbering change sets from 1 instead of 0? 🤔
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 don’t know 😄
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.
Blasphemous behavior. 😈
|
||
class RequisitionStatusTransitionEventService { | ||
static transactional = true |
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.
Is this transactional really necessary? (I know that you marked it to remember adding a transaction in Grails 3, but I am not sure if there is a need for the transaction)
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 added it when searching for solutions for the Lazy issue mentioned above. I can remove it if you don’t like it
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 this is not required anymore, then you should remove it
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 we're just doing reads then we don't need a transaction. If you're writing to the database then we need a transaction.
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 general I would not agree, because not having a transaction boundary also causes LazyInitializationExceptions while reading (assuming you don't use osiv). Anyway as I mentioned, it was actually added for debugging purposes and finding a solution for the Lazy that was thrown.
I will remove it.
@@ -121,6 +121,7 @@ class Requisition implements Comparable<Requisition>, Serializable { | |||
|
|||
// Request approval fields | |||
Person approvedBy | |||
Person rejectedBy |
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 know that @drodzewicz also added the migration for rejectedBy within the ticket that he is working on, so we have to remember to avoid duplicating them
Boolean isApprovalRequired() { | ||
return supportedActivities?.contains(ActivityCode.APPROVE_REQUEST.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.
I think it should use supports
@@ -9,6 +9,7 @@ | |||
**/ | |||
package org.pih.warehouse.core | |||
|
|||
import org.pih.warehouse.auth.AuthService |
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.
Is this required?
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.
Not anymore, forgot to delete it in the latest commit.
@@ -150,7 +151,8 @@ class Requisition implements Comparable<Requisition>, Serializable { | |||
statusSortOrder formula: RequisitionStatus.getStatusSortOrderFormula() | |||
monthRequested formula: "date_format(date_requested, '%M %Y')" | |||
comments joinTable: [name: "requisition_comment", key: "requisition_id"] | |||
events joinTable: [name: "requisition_event", key: "requisition_id"] | |||
// Fetch join in the events should be removed in Grails 3 and replaced with proper join query using data services | |||
events joinTable: [name: "requisition_event", key: "requisition_id"], fetch: '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.
I am not sure about that one, @jmiranda what's your opinion here?
<insert tableName="event_type"> | ||
<column name="id" value="abb5b3d2-da3e-4a4c-9e3c-de139147f822"/> | ||
<column name="version" valueNumeric="0"/> | ||
<column name="date_created" valueDate="2023-09-21T00:00:00.0"/> |
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.
Perhaps it's better to use <property name="now" value="now()"/>
for these dates.
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.
Interesting. I hadn't really thought about the use of now() until you mentioned it here. I'm not sure I agree, but I'm fine with either approach. There's something to be said for having a static value for static data to ensure that everyone has the exact same data, down to the timestamps.
There's also an argument for knowing the exact time the data was created in case you are troubleshooting a database migration issue during deployment (i.e. a failed precondition).
And just from an aesthetic point of view, sometimes when I do a select * from event_type
or some other static data table, there's a nice fuzzy feeling of seeing 2012 in the date_created. That is usually followed quickly with dread and despair of knowing I'm old af.
Ok then to prevent existential crises ... let's use now().
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.
Definitely we should avoid existential crises 😆 so I'll change that
<column name="event_code" value="PENDING_APPROVAL"/> | ||
<column name="last_updated" valueDate="2023-09-21T00:00:00.0"/> | ||
<column name="name" value="Waiting for approval"/> | ||
<column name="sort_order" valueNumeric="1"/> |
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 not sure if these sort orders make sense, but I am not sure if this is currently used for anything at all.
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.
It's a good question. If we ever do
event1 > event2
then we probably need to deal with it.
But I assume we're using the ordinal value on the EventCode to do the sorting, so probably not an issue.
I'm ok leaving this for now OR changing sort order to 0.
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.
it seems not to be used anywhere at the moment, but since the default event types also have the sortOrder, I decided to add it.
Not to cause confusions I can either remove it (since it is nullable) or change it to 0. Let me know.
|
||
class RequisitionStatusTransitionEventService { | ||
static transactional = true |
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 this is not required anymore, then you should remove it
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 can't remember if I requested changes, so just leaving this as a comment. LGTM.
Boolean isApprovalRequired() { | ||
return supportedActivities?.contains(ActivityCode.APPROVE_REQUEST.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.
Agreed
@@ -150,7 +151,8 @@ class Requisition implements Comparable<Requisition>, Serializable { | |||
statusSortOrder formula: RequisitionStatus.getStatusSortOrderFormula() | |||
monthRequested formula: "date_format(date_requested, '%M %Y')" | |||
comments joinTable: [name: "requisition_comment", key: "requisition_id"] | |||
events joinTable: [name: "requisition_event", key: "requisition_id"] | |||
// Fetch join in the events should be removed in Grails 3 and replaced with proper join query using data services | |||
events joinTable: [name: "requisition_event", key: "requisition_id"], fetch: '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.
I would probably try a withNewSession in the event handler.
Requisition.withNewSession {
requisition = Requisition.read() // or Requisition.get() if you need to set something
}
Or if that doesn't work, just do a Requisition.read(id) or get(id) in the event handler.
Lastly, you could potentially use refresh the event handler but that one always scares me.
requisition.refresh()
Not sure if you need the withSession or withNewSession wrapper so I'll leave that as an exercise for the reader.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"> | ||
|
||
<changeSet author="kchelstowski" id="210920231330-1"> |
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.
Blasphemous behavior. 😈
<insert tableName="event_type"> | ||
<column name="id" value="abb5b3d2-da3e-4a4c-9e3c-de139147f822"/> | ||
<column name="version" valueNumeric="0"/> | ||
<column name="date_created" valueDate="2023-09-21T00:00:00.0"/> |
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.
Interesting. I hadn't really thought about the use of now() until you mentioned it here. I'm not sure I agree, but I'm fine with either approach. There's something to be said for having a static value for static data to ensure that everyone has the exact same data, down to the timestamps.
There's also an argument for knowing the exact time the data was created in case you are troubleshooting a database migration issue during deployment (i.e. a failed precondition).
And just from an aesthetic point of view, sometimes when I do a select * from event_type
or some other static data table, there's a nice fuzzy feeling of seeing 2012 in the date_created. That is usually followed quickly with dread and despair of knowing I'm old af.
Ok then to prevent existential crises ... let's use now().
<column name="event_code" value="PENDING_APPROVAL"/> | ||
<column name="last_updated" valueDate="2023-09-21T00:00:00.0"/> | ||
<column name="name" value="Waiting for approval"/> | ||
<column name="sort_order" valueNumeric="1"/> |
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.
It's a good question. If we ever do
event1 > event2
then we probably need to deal with it.
But I assume we're using the ordinal value on the EventCode to do the sorting, so probably not an issue.
I'm ok leaving this for now OR changing sort order to 0.
|
||
class RequisitionStatusTransitionEventService { | ||
static transactional = true |
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 we're just doing reads then we don't need a transaction. If you're writing to the database then we need a transaction.
@@ -230,6 +231,7 @@ class StockMovementService { | |||
void updateRequisitionStatus(String id, RequisitionStatus status) { | |||
|
|||
log.info "Update status ${id} " + status | |||
// TODO: In Grails the get below should be replaced by the data service get that joins the Events |
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 could (but don't have to) add a getWithEvents() to the Requisition class now.
@@ -244,6 +246,7 @@ class StockMovementService { | |||
} else { | |||
requisition.status = status | |||
requisition.save(flush: true) | |||
publishEvent(new RequisitionStatusTransitionEvent(requisition, requisition.status)) |
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 could pass the requisition.id here in order to force a read or get on the event service. We could think about doing that across all of our events.
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.
Is there any need to pass both the from and to status 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.
Could you elaborate on what you mean by "from and to" status? I pass only a new status (I got rid of the oldStatus, because I feel like it is not needed).
Event createEvent(EventCode eventCode, Location eventLocation, Date eventDate, User currentUser) { | ||
EventType eventType = EventType.findByEventCode(eventCode) | ||
if (!eventType) { | ||
throw new NotFoundException("No event type with code ${eventCode} has been found") |
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.
Good call on the exception.
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 it LGTM
…rkflow (#4288) * OBPIH-5803 Add approvalRequired transient to Location domain * OBPIH-5803 Change fetchMode of Requisition events to avoid LazyInitializationException in a published event * OBPIH-5803 Add request approval statuses to enums and add translations for them * OBPIH-5803 Include status transition in RequisitionStatusTransitionEvent * OBPIH-5803 Add publishing a status transition event while submitting a request * OBPIH-5803 Add transition status method in RequisitionService * OBPIH-5803 Change displayed status on SM view page to look for stockMovement status, not shipment status * OBPIH-5803 Add createdBy field for Event domain * OBPIH-5803 Add rejectedBy field to Requisition * OBPIH-5803 Display correct submit button depending on request approval activity and remove confirm submit modal * OBPIH-5803 Remove unnecessary oldStatus property from RequisitionStatusTransitionEvent * OBPIH-5803 Include new requisition statuses in the sortOrder formula * OBPIH-5803 Add default EventTypes for request approval workflow * OBPIH-5803 Fix assigning createdBy to an Event * OBPIH-5803 Fixes after review
No description provided.