Index: build.properties =================================================================== --- build.properties (revision 14204) +++ build.properties (working copy) @@ -9,7 +9,7 @@ build.compiler=modern openmrs.version.major=1 -openmrs.version.minor=8 +openmrs.version.minor=9 openmrs.version.maintenance=0 openmrs.version.suffix=dev Index: metadata/api/hibernate/org/openmrs/api/db/hibernate/Encounter.hbm.xml =================================================================== --- metadata/api/hibernate/org/openmrs/api/db/hibernate/Encounter.hbm.xml (revision 14204) +++ metadata/api/hibernate/org/openmrs/api/db/hibernate/Encounter.hbm.xml (working copy) @@ -69,13 +69,14 @@ - - - - + + + + Index: metadata/model/liquibase-update-to-latest.xml =================================================================== --- metadata/model/liquibase-update-to-latest.xml (revision 14204) +++ metadata/model/liquibase-update-to-latest.xml (working copy) @@ -3247,35 +3247,35 @@ - - - - - - - - Adding daemon user to users table - - - - - - - - - - - - Removing scheduler.username and scheduler.password global properties - - property = 'scheduler.username' - - - property = 'scheduler.password' - - + + + + + + + + Adding daemon user to users table + + + + + + + + + + + + Removing scheduler.username and scheduler.password global properties + + property = 'scheduler.username' + + + property = 'scheduler.password' + + @@ -3297,237 +3297,237 @@ - + Switch boolean concepts/observations to be stored as coded - - - - Drop Not-Null constraint from location column in Encounter and Obs table - - - - - - - - - - - Changing the default value to 2 for 'message_state' column in 'hl7_in_archive' table - - - - - - - - - Converting 0 and 1 to 2 for 'message_state' column in 'hl7_in_archive' table - - - message_state IN (0,1) - - - - - - Removing the duplicate privilege 'Add Concept Proposal' in favor of 'Add Concept Proposals' - - - Add Concept Proposals - privilege = 'Add Concept Proposal' and not exists (select * from (select role, privilege from role_privilege) rp2 where rp2.role = role and rp2.privilege = 'Add Concept Proposals') - - privilege='Add Concept Proposal' - privilege='Add Concept Proposal' - - - - - Removing the duplicate privilege 'Edit Concept Proposal' in favor of 'Edit Concept Proposals' - - - Edit Concept Proposals - privilege = 'Edit Concept Proposal' and not exists (select * from (select role, privilege from role_privilege) rp2 where rp2.role = role and rp2.privilege = 'Edit Concept Proposals') - - privilege='Edit Concept Proposal' - privilege='Edit Concept Proposal' - - - - - - - - - - Create active list type table. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create active list table - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create allergen table - - - - - - - - - - - - - - - - - - Create problem table - - - - - - - - - - - - - SELECT count(*) FROM active_list_type - - - Inserting default active list types - - - - - - - - - - - - - - - - - - - - - - + + + + Drop Not-Null constraint from location column in Encounter and Obs table + + + + + + + + + + + Changing the default value to 2 for 'message_state' column in 'hl7_in_archive' table + + + + + + + + + Converting 0 and 1 to 2 for 'message_state' column in 'hl7_in_archive' table + + + message_state IN (0,1) + + + + Removing the duplicate privilege 'Add Concept Proposal' in favor of 'Add Concept Proposals' + + + Add Concept Proposals + privilege = 'Add Concept Proposal' and not exists (select * from (select role, privilege from role_privilege) rp2 where rp2.role = role and rp2.privilege = 'Add Concept Proposals') + + privilege='Add Concept Proposal' + privilege='Add Concept Proposal' + + + + + Removing the duplicate privilege 'Edit Concept Proposal' in favor of 'Edit Concept Proposals' + + + Edit Concept Proposals + privilege = 'Edit Concept Proposal' and not exists (select * from (select role, privilege from role_privilege) rp2 where rp2.role = role and rp2.privilege = 'Edit Concept Proposals') + + privilege='Edit Concept Proposal' + privilege='Edit Concept Proposal' + + + + + + + + + + Create active list type table. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create active list table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create allergen table + + + + + + + + + + + + + + + + + + Create problem table + + + + + + + + + + + + + SELECT count(*) FROM active_list_type + + + Inserting default active list types + + + + + + + + + + + + + + + + + + + + + + + + @@ -3663,5 +3663,39 @@ - - + + + + + + + + + + Allow multiple providers per encounter (see ticket #2369) + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: src/api/org/openmrs/Encounter.java =================================================================== --- src/api/org/openmrs/Encounter.java (revision 14204) +++ src/api/org/openmrs/Encounter.java (working copy) @@ -14,10 +14,12 @@ package org.openmrs; import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeSet; /** * An Encounter represents one visit or interaction of a patient with a healthcare worker. Every @@ -48,16 +50,17 @@ private EncounterType encounterType; - private Person provider; - private Set orders; private Set obs; + private Set providers; + // Constructors /** default constructor */ public Encounter() { + providers = createSortablePersons(); } /** @@ -65,6 +68,7 @@ * @should set encounter id */ public Encounter(Integer encounterId) { + this(); this.encounterId = encounterId; } @@ -80,7 +84,8 @@ * @should have equal encounter objects with no encounter ids * @should not have equal encounter objects when one has null encounter id */ - public boolean equals(Object obj) { + @Override + public boolean equals(Object obj) { if (obj instanceof Encounter) { Encounter enc = (Encounter) obj; if (this.getEncounterId() != null && enc.getEncounterId() != null) @@ -101,7 +106,8 @@ * @should have different hash code when not equal * @should get hash code with null attributes */ - public int hashCode() { + @Override + public int hashCode() { if (this.getEncounterId() == null) return super.hashCode(); return this.getEncounterId().hashCode(); @@ -403,27 +409,80 @@ /** * @return Returns the provider. * @since 1.6 (used to return User) + * @deprecated use{@link #getProviders()} + * @should return null if there is no providers + * @should return the first Person object from the Providers collection sorted by Person_Id + * Ascending */ + @Deprecated public Person getProvider() { - return provider; + if (!providers.isEmpty()) { + return providers.iterator().next(); + } + return null; } /** * @param provider The provider to set. - * @deprecated use {@link #setProvider(Person)} + * @deprecated use {@link #addProvider(Person)} */ + @Deprecated public void setProvider(User provider) { setProvider(provider.getPerson()); } /** - * @param provider The provider to set. - */ - public void setProvider(Person provider) { - this.provider = provider; - } - + * @param provider The provider to set. + * @deprecated use {@link #addProvider(Person)} + */ + @Deprecated + public void setProvider(Person provider) { + addProvider(provider); + } + /** + * @return Returns the providers + * @since 1.9 + */ + public Set getProviders() { + return providers; + } + + /** + * @param provider a new provider + * @since 1.9 + */ + public void addProvider(Person provider) { + if (!providers.contains(provider)) { + providers.add(provider); + } + } + + /** + * @param provider a provider is removed + * @since 1.9 + */ + public boolean removeProvider(Person provider) { + return providers.remove(provider); + } + + /** + * @param providers The providers to set + * @since 1.9 + */ + public void setProviders(Set providers) { + Set sortedProviders = createSortablePersons(); + if (providers != null) { + sortedProviders.addAll(providers); + } + this.providers = sortedProviders; + } + + public void clearProviders() { + providers.clear(); + } + + /** * @return Returns the form. */ public Form getForm() { @@ -450,6 +509,7 @@ ret += this.getLocation() == null ? "(no Location) " : this.getLocation().getName() + " "; ret += this.getPatient() == null ? "(no Patient) " : this.getPatient().getPatientId().toString() + " "; ret += this.getForm() == null ? "(no Form) " : this.getForm().getName() + " "; + ret += this.getProviders().size() == 0 ? "(no Providers) " : "num Providers: " + this.getProviders().size() + " "; ret += this.getObsAtTopLevel(false) == null ? "(no Obss) " : "num Obs: " + this.getObsAtTopLevel(false) + " "; ret += this.getOrders() == null ? "(no Orders) " : "num Orders: " + this.getOrders().size() + " "; return "Encounter: [" + ret + "]"; @@ -470,7 +530,31 @@ */ public void setId(Integer id) { setEncounterId(id); - } + /** + * Created empty TreeSet collection and sorted by Person_Id in ascending order * + * + * @return Set + * @since 1.9 + */ + public static final Set createSortablePersons() { + return new TreeSet(new Comparator() { + + @Override + public int compare(Person person1, Person person2) { + if (person1 == null) { + return -1; + } else if (person2 == null) { + return 1; + } + if (person1.getPersonId() < person2.getPersonId()) { + return -1; + } else if (person1.getPersonId() > person2.getPersonId()) { + return 1; + } + return 0; + } + }); + } } Index: src/web/org/openmrs/web/controller/encounter/EncounterFormController.java =================================================================== --- src/web/org/openmrs/web/controller/encounter/EncounterFormController.java (revision 14204) +++ src/web/org/openmrs/web/controller/encounter/EncounterFormController.java (working copy) @@ -15,8 +15,10 @@ import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.Vector; @@ -34,6 +36,7 @@ import org.openmrs.FormField; import org.openmrs.Location; import org.openmrs.Obs; +import org.openmrs.Person; import org.openmrs.api.EncounterService; import org.openmrs.api.FormService; import org.openmrs.api.context.Context; @@ -64,6 +67,25 @@ protected final Log log = LogFactory.getLog(getClass()); /** + * @see org.springframework.web.servlet.mvc.BaseCommandController#onBind(javax.servlet.http.HttpServletRequest, + * java.lang.Object) + */ + @Override + protected void onBind(HttpServletRequest request, Object command) throws Exception { + + Encounter encounter = (Encounter) command; + + if (Context.isAuthenticated()) { + String[] providerIds = fetchProviderIds(request); + if (providerIds != null) { + updateProvidersOfEncounter(encounter, providerIds); + } + } + + super.onBind(request, encounter); + } + + /** * Allows for Integers to be used as values in input tags. Normally, only strings and lists are * expected * @@ -100,14 +122,14 @@ if (StringUtils.hasText(request.getParameter("patientId"))) encounter.setPatient(Context.getPatientService().getPatient( Integer.valueOf(request.getParameter("patientId")))); - if (StringUtils.hasText(request.getParameter("providerId"))) - encounter.setProvider(Context.getPersonService().getPerson( - Integer.valueOf(request.getParameter("providerId")))); + if (encounter.isVoided()) ValidationUtils.rejectIfEmptyOrWhitespace(errors, "voidReason", "error.null"); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "patient", "error.null"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "provider", "error.null"); + if (fetchProviderIds(request) == null) { + errors.rejectValue("providers", "error.null", null, null); + } ValidationUtils.rejectIfEmptyOrWhitespace(errors, "encounterDatetime", "error.null"); } @@ -129,7 +151,7 @@ * org.springframework.validation.BindException) */ @Override - protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object obj, + protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object obj, BindException errors) throws Exception { HttpSession httpSession = request.getSession(); @@ -148,9 +170,6 @@ encounter.setPatient(Context.getPatientService().getPatient( Integer.valueOf(request.getParameter("patientId")))); - // set the provider if they changed it - encounter.setProvider(Context.getPersonService().getPerson(Integer.valueOf(request.getParameter("providerId")))); - if (encounter.isVoided() && encounter.getVoidedBy() == null) // if this is a "new" voiding, call voidEncounter to set appropriate attributes Context.getEncounterService().voidEncounter(encounter, encounter.getVoidReason()); @@ -181,24 +200,61 @@ * @see org.springframework.web.servlet.mvc.AbstractFormController#formBackingObject(javax.servlet.http.HttpServletRequest) */ @Override - protected Object formBackingObject(HttpServletRequest request) throws ServletException { + protected Object formBackingObject(HttpServletRequest request) throws ServletException { Encounter encounter = null; + if (Context.isAuthenticated()) { + encounter = fetchEncounter(request); + } + + return encounter; + } + + private void updateProvidersOfEncounter(Encounter encounter, String[] newProviderIds) { - if (Context.isAuthenticated()) { - EncounterService es = Context.getEncounterService(); - String encounterId = request.getParameter("encounterId"); - if (encounterId != null) { - encounter = es.getEncounter(Integer.valueOf(encounterId)); + Set providers = encounter.getProviders(); + + Map mapExistingProviders = new HashMap(); + for (Person person : providers) { + mapExistingProviders.put(person.getPersonId().toString(), person); + } + Set newProviders = new HashSet(); + for (String providerId : newProviderIds) { + Person currentPerson = mapExistingProviders.get(providerId); + if (currentPerson == null) { + newProviders.add(Context.getPersonService().getPerson(Integer.valueOf(providerId))); + } else { + newProviders.add(currentPerson); } } + encounter.setProviders(newProviders); - if (encounter == null) - encounter = new Encounter(); + } + + private Encounter fetchEncounter(HttpServletRequest request) { + Encounter encounter = null; + EncounterService es = Context.getEncounterService(); + String encounterId = request.getParameter("encounterId"); + if (encounterId != null) { + encounter = es.getEncounter(Integer.valueOf(encounterId)); + return encounter; + } else { + Encounter emptyEncounter = new Encounter(); + emptyEncounter.setProviders(new HashSet()); + return emptyEncounter; + } - return encounter; } + private String[] fetchProviderIds(HttpServletRequest request) { + String[] providerIds = null; + String strProviders = request.getParameter("encounterProviders"); + if (StringUtils.hasText(strProviders)) { + providerIds = strProviders.split(" "); + } + return providerIds; + } + /** * @see org.springframework.web.servlet.mvc.SimpleFormController#referenceData(javax.servlet.http.HttpServletRequest, * java.lang.Object, org.springframework.validation.Errors) @@ -246,7 +302,7 @@ //get the obs that was not created with the original encounter Encounter en = o.getEncounter(); - if(o.getDateCreated().compareTo(en.getDateCreated())!=0){ + if (o.getDateCreated().compareTo(en.getDateCreated()) != 0) { obsAfterEncounter.add(o.getId()); } @@ -269,7 +325,7 @@ ff = new FormField(); // we only put the top-level obs in the obsMap. Those would - // be the obs that don't have an obs grouper + // be the obs that don't have an obs grouper if (o.getObsGroup() == null) { // populate the obs map with this formfield and obs List list = obsMapToReturn.get(ff); @@ -294,7 +350,7 @@ map.put("locale", Context.getLocale()); map.put("editedObs", editedObs); - + map.put("obsAfterEncounter", obsAfterEncounter); return map; Index: src/web/org/openmrs/web/dwr/EncounterListItem.java =================================================================== --- src/web/org/openmrs/web/dwr/EncounterListItem.java (revision 14204) +++ src/web/org/openmrs/web/dwr/EncounterListItem.java (working copy) @@ -14,10 +14,13 @@ package org.openmrs.web.dwr; import java.util.Date; +import java.util.Iterator; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.Encounter; +import org.openmrs.Person; import org.openmrs.PersonName; import org.openmrs.util.Format; @@ -47,7 +50,6 @@ } public EncounterListItem(Encounter encounter) { - if (encounter != null) { encounterId = encounter.getEncounterId(); encounterDateTime = encounter.getEncounterDatetime(); @@ -62,8 +64,17 @@ if (pn.getFamilyName() != null) PersonName += " " + pn.getFamilyName(); } - if (encounter.getProvider() != null) - providerName = encounter.getProvider().getPersonName().toString(); + if (encounter.getProviders() != null && !encounter.getProviders().isEmpty()) { + Set persons = encounter.getProviders(); + StringBuffer sb = new StringBuffer(); + for (Iterator personIterator = persons.iterator(); personIterator.hasNext();) { + sb.append(personIterator.next().getPersonName()); + if (personIterator.hasNext()) { + sb.append(",
"); + } + } + providerName = sb.toString(); + } if (encounter.getLocation() != null) location = encounter.getLocation().getName(); if (encounter.getEncounterType() != null) Index: test/api/org/openmrs/api/EncounterServiceTest.java =================================================================== --- test/api/org/openmrs/api/EncounterServiceTest.java (revision 14204) +++ test/api/org/openmrs/api/EncounterServiceTest.java (working copy) @@ -146,8 +146,12 @@ enc.setLocation(new Location(1)); enc.setEncounterType(new EncounterType(1)); enc.setEncounterDatetime(new Date()); - enc.setPatient(new Patient(3)); - enc.setProvider(new Person(1)); + Patient patient = new Patient(3); + patient.setGender("M"); + enc.setPatient(patient); + Person provider = new Person(1); + provider.setGender("M"); + enc.setProvider(provider); es.saveEncounter(enc); // Now add an obs to it Index: test/api/org/openmrs/EncounterTest.java =================================================================== --- test/api/org/openmrs/EncounterTest.java (revision 14204) +++ test/api/org/openmrs/EncounterTest.java (working copy) @@ -16,6 +16,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.Date; @@ -901,4 +902,34 @@ encounter.removeOrder(new Order(123)); } + /** + * @see {@link Encounter#getProvider()} + */ + @Test + @Verifies(value = "should return null if there is no providers", method = "getProvider()") + public void getProvider_shouldReturnNullIfThereIsNoProviders() throws Exception { + Encounter encounter = new Encounter(); + assertNull(encounter.getProvider()); + } + + /** + * @see {@link Encounter#getProvider()} + */ + @Test + @Verifies(value = "should return the first Person object from the Providers collection sorted by Person_Id Ascending", method = "getProvider()") + public void getProvider_shouldReturnTheFirstPersonObjectFromTheProvidersCollectionSortedByPerson_IdAscending() + throws Exception { + Encounter encounter = new Encounter(); + Person provider = new Person(3); + encounter.addProvider(provider); + + provider = new Person(1); + encounter.addProvider(provider); + + provider = new Person(2); + encounter.addProvider(provider); + + assertEquals(new Integer(1), encounter.getProvider().getPersonId()); + } + } Index: web/WEB-INF/messages.properties =================================================================== --- web/WEB-INF/messages.properties (revision 14204) +++ web/WEB-INF/messages.properties (working copy) @@ -686,6 +686,7 @@ Encounter.type=Encounter Type Encounter.location=Location Encounter.provider=Provider +Encounter.providers=Providers Encounter.provider.find=Find Provider Encounter.datetime=Encounter Date Encounter.find=Find Encounter Index: web/WEB-INF/openmrs_static_content-servlet.xml =================================================================== --- web/WEB-INF/openmrs_static_content-servlet.xml (revision 14204) +++ web/WEB-INF/openmrs_static_content-servlet.xml (working copy) @@ -18,6 +18,7 @@ jstlContentController jstlContentController + jstlContentController jstlContentController jstlContentController jstlContentController Index: web/WEB-INF/view/admin/encounters/encounterForm.jsp =================================================================== --- web/WEB-INF/view/admin/encounters/encounterForm.jsp (revision 14204) +++ web/WEB-INF/view/admin/encounters/encounterForm.jsp (working copy) @@ -18,6 +18,37 @@ +