Repository navigation
Conversation
|
chibongho
left a comment
There was a problem hiding this comment.
Looks good. Thanks for the fix!
mogoodrich
left a comment
There was a problem hiding this comment.
One small question about the date voided but generally LGTM. So interesting that Claude points out our somewhat misuse of how Hibernate works.
| for (BedPatientAssignment bpa : bpaList) { | ||
| bedManagementService.deleteBedPatientAssignment(bpa, "encounter voided"); | ||
| bpa.setVoided(true); | ||
| bpa.setDateVoided(date); |
There was a problem hiding this comment.
Not sure what date is passed in here, I would think this should always be the current datetime? (maybe it already is)
There was a problem hiding this comment.
You are correct @mogoodrich :
Yes, for a SaveHandler the date is always the current datetime
SaveHandlers are driven by RequiredDataAdvice (the MethodBeforeAdvice on save* service methods). The save path goes through the 3-arg recursivelyHandle(Class, OpenmrsObject, String) overload, whose bytecode is unambiguous:
2: invokestatic Context.getAuthenticatedUser() // -> user
5: new java/util/Date
9: invokespecial java/util/Date."":()V // -> new Date() == "now"
17: invokestatic recursivelyHandle(Class, OpenmrsObject, User, Date, String, Set)
That new Date() is then handed straight to each handler via RequiredDataHandler.handle(object, user, date, reason) (the #150 interface call at offset 71). So the date your handler receives is a fresh new Date() captured at the start of the save operation — i.e. the current datetime.
That makes bpa.setDateVoided(date) equivalent to the original deleteBedPatientAssignment's bpa.setDateVoided(new Date()) — just sourced from the timestamp the framework already created for this save, so the void instant lines up with the rest of that operation's audit metadata. No behavior change, and no risk of it being null.
One nuance worth knowing (doesn't affect us)
The other branches in RequiredDataAdvice — the Voidable/Retireable paths that drive VoidHandler/RetireHandler — behave differently: they pass the object's existing getDateVoided() / getDateRetired() if it's already set, and only fall back to new Date() otherwise (visible at offsets 253–303 and 384–434). So for those handler types, the date is "whenever it was voided," not necessarily now.
But our class is a SaveHandler, which is only ever invoked through the save path → always new Date(). So your instinct is correct: for this handler it's always the current datetime.
No code change needed — the date parameter is the right thing to use here.
|
Thanks @chibongho and @mogoodrich ! |



I applied the fix to EncounterWithBedPatientAssignmentSaveHandler.handle and removed both calls to the @transactional bed-management service that were forcing the premature flush:
Both bpa instances come from a readOnly query so they're persistent and attached; their changes now flush with the surrounding transaction, after the unrelated VisitAttribute has had its valueReference populated — so the DataIntegrityViolationException no longer fires.
The bedManagementService field is retained because it's still used for the read-only getBedPatientAssignmentByEncounter lookup, which doesn't trigger a flush.