OBPIH-7597 Allow mass Recipient Edit for line items in multiple POs#5727
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #5727 +/- ##
============================================
+ Coverage 9.12% 9.85% +0.73%
- Complexity 1170 1377 +207
============================================
Files 701 732 +31
Lines 45281 46135 +854
Branches 10851 11011 +160
============================================
+ Hits 4131 4546 +415
- Misses 40497 40884 +387
- Partials 653 705 +52 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| InternetAddress internetAddress = new InternetAddress(recipient, false) | ||
| internetAddress.validate() |
There was a problem hiding this comment.
I don't understand that part
There was a problem hiding this comment.
Here we use InternetAddress only to detect whether the value entered in the recipient column is an email address (e.g. alannadolny@gmail.com or Alan Nadolny <alannadolny@gmail.com>). Based on that, we decide whether to check the person by email (getActivePersonByEmail method) or by full name (getActivePersonByName method). You can read more about this here: https://docs.oracle.com/javaee/5/api/javax/mail/internet/InternetAddress.html
I was inspired by @ewaterman code:
Person getOrCreatePersonByRecipient(String recipient) {
if (StringUtils.isBlank(recipient)) {
return null
}
InternetAddress internetAddress
try {
internetAddress = new InternetAddress(recipient, false)
}
catch (AddressException ignored) {
// If recipient isn't a valid internet address, it must be a regular name.
return getOrCreatePersonFromNames(recipient)
}
// If a person exists with the given email, return them. Note that this can return inactive people so
// the caller must properly handle that case. We do this to avoid creating duplicate users.
Person person = getPersonByEmail(internetAddress.address)
if (person) {
return person
}
// Otherwise create a new person.
if (!internetAddress.personal) {
throw new RuntimeException("Cannot save new recipient without a name: ${recipient}")
}
String[] names = extractNames(internetAddress.personal)
person = new Person(firstName: names[0], lastName: names[1], email: internetAddress.address)
if (!person.save(flush: true)) {
throw new ValidationException("Cannot save recipient ${recipient} due to errors", person.errors)
}
return person
}
There was a problem hiding this comment.
I forgot I wrote that 😅 I think the code in PersonService can be refactored so that we have cleaner methods:
- getPersonByName(String) -> split into firstname, lastname and query
- getPersonByEmail(String) -> simply query on Person.email
- getPerson(String) -> calls both getPersonByEmail and getPersonByName to find the Person
- getActivePerson(String) -> calls getPerson then returns them if active == true
- getOrCreatePerson(String) -> calls getPerson and if null, creates a Person
but I won't make you do that.
There was a problem hiding this comment.
The refactor looks simple enough, so I can try tomorrow
There was a problem hiding this comment.
btw, the internet address looks weird to me, and I am confused every time I see that code 😄 Maybe we can try to do some kind of validator for that? (https://grails.apache.org/docs/7.0.4/ref/Constraints/email.html)
There was a problem hiding this comment.
the problem is that when we import from a csv or xls file, we have just one field representing the Person (in this case "recipient"). And to be user friendly, we want to accept either name or email in that field. So we can't just wrap the field in a simple built-in validator. The String field that we get is either an email or a name and we need to figure out which it is here
| if (!StringUtils.isBlank(params["recipient"])) { | ||
| Person recipient = personService.getActivePerson(params["recipient"]) | ||
| if (!recipient) { | ||
| command.errors.reject("Row ${index + 1}: Recipient ${params["recipient"]} does not exist in OpenBoxes. Please check your data") |
There was a problem hiding this comment.
you can assign the params["recipient"] to a variable
b9c9250 to
9085eb2
Compare
| Person getActivePerson(String recipient) { | ||
| Person person = getPerson(recipient) | ||
| return person?.active ? person : null | ||
| } |
There was a problem hiding this comment.
looks good! I know you didn't introduce it, but "recipient" doesn't make much sense here IMO because this can be used by any flow, not just orders and shipments. Really it's just a string for identifying a person.
Can you please change the "recipient" variables here to "identifier" or "personIdentifier" or something similar?
There was a problem hiding this comment.
yes, I think that "identifier" makes more sense. I will change that
| 'Justin Miranda' || '1' | ||
| 'Unknown Name <justin@openboxes.com>' || '1' | ||
| 'Justin Inactive' || null | ||
| } |
There was a problem hiding this comment.
Nice tests! I feel you could be more exhaustive with inputs here. Both these methods could test:
- 'Justin'
- 'Justin Miranda'
- 'Justin Justy Miranda'
- 'justin@openboxes.com'
- 'unknown@openboxes.com'
- 'Justin Miranda <justin@openboxes.com>'
- 'Justin Miranda <unknown@openboxes.com>'
- 'Unknown Name <justin@openboxes.com>'
- 'Justin Inactive'
- null
There was a problem hiding this comment.
I agree it’s a good idea to test more cases. I haven’t written many tests in my career, so thanks for the guidance on what exactly to test 🚀
| 'justin@openboxes.com' || '1' | ||
| 'unknown@openboxes.com' || null | ||
| 'Justin Miranda <justin@openboxes.com>' || '1' | ||
| 'Justin Miranda <unknown@openboxes.com>' || null |
There was a problem hiding this comment.
I think that this test case is curious. In this test case: Justin Miranda <unknown@openboxes.com> the result is null. This happens because the method detects an email format and only searches by the email address. Even though the name 'Justin Miranda' is correct, the system doesn't find the person because the email is unknown.
On the other hand, if the email is correct but the name is wrong (e.g. Unknown Name <justin@openboxes.com>), the system finds the person.
Maybe somehow we should improve this? If the email search fails, then we should also check by name or maybe we should stay as it is and always prioritize email 🤔
There was a problem hiding this comment.
hmm it's an interesting point. I don't know if there's a clear answer.
Another argument is that if you provide both, both should match. Person isn't actually unique on email or on name so just because the name or email match, it doesn't mean you're getting the user that you want. Providing both could mean you want to be very specific. But idk, that could be overly restrictive.
I think it's more important that the email match because email far more likely to be unique. Two people can have the same name, but I'd be surprised if we have two people with the same email (unless the same person is in the system multiple times).
So given that, I guess the current logic makes sense to prioritize email, but I suppose we could have it try name afterwards if the email doesn't match.
That said, I don't actually know if anyone is providing strings like Justin Miranda <unknown@openboxes.com>. I imagine 99% of the time it's either just name or just email. So maybe it's not a problem that we need to deal with
There was a problem hiding this comment.
@kchelstowski @alannadolny what do you think about this?
There was a problem hiding this comment.
I believe it's not a problem because it's a hardcore edge case. But maybe as a simple solution, we can show the user an error like: "We found a person by the name that you provided, but the email doesn't match. Please check your data."
There was a problem hiding this comment.
hmmmm I think it's better to leave it as it is 🤔
✨ Description of Change
Link to GitHub issue or Jira ticket:
https://pihemr.atlassian.net/browse/OBPIH-7597
Description:
📷 Screenshots & Recordings (optional)