diff --git a/api/src/main/java/org/openmrs/Patient.java b/api/src/main/java/org/openmrs/Patient.java index f535b9b95e4b..a21b2028b471 100644 --- a/api/src/main/java/org/openmrs/Patient.java +++ b/api/src/main/java/org/openmrs/Patient.java @@ -11,12 +11,26 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.SortNatural; import org.hibernate.search.annotations.ContainedIn; +import org.hibernate.search.annotations.Field; /** * Defines a Patient in the system. A patient is simply an extension of a person and all that that @@ -24,17 +38,60 @@ * * @version 2.0 */ +@Entity +@Table(name = "patient") +@PrimaryKeyJoinColumn(name = "patient_id") public class Patient extends Person { public static final long serialVersionUID = 93123L; + @Column(name = "patient_id", nullable = false, updatable = false, insertable = false) private Integer patientId; + @Column(name = "allergy_status", length = 50) private String allergyStatus = Allergies.UNKNOWN; + @OneToMany(mappedBy = "patient", cascade = CascadeType.ALL, orphanRemoval = true) + @SortNatural @ContainedIn private Set identifiers; + @ManyToOne + @JoinColumn(name = "creator", updatable = false) + @Access(AccessType.PROPERTY) + private User creator; + + @Column(name = "date_created", nullable = false, updatable = false, length = 19) + @Access(AccessType.PROPERTY) + private Date dateCreated; + + @Column(name = "voided", nullable = false) + @Access(AccessType.PROPERTY) + @Field + private Boolean voided = Boolean.FALSE; + + @Column(name = "date_voided", length = 19) + @Access(AccessType.PROPERTY) + private Date dateVoided; + + @ManyToOne + @JoinColumn(name = "voided_by") + @Access(AccessType.PROPERTY) + private User voidedBy; + + @Column(name = "void_reason") + @Access(AccessType.PROPERTY) + private String voidReason; + + @ManyToOne + @JoinColumn(name = "changed_by") + @Access(AccessType.PROPERTY) + private User changedBy; + + @Column(name = "date_changed", length = 19) + @Access(AccessType.PROPERTY) + private Date dateChanged; + // Constructors /** default constructor */ @@ -99,6 +156,9 @@ public Patient(Patient patient) { * @return internal identifier for patient */ public Integer getPatientId() { + if (this.patientId == null) { + this.patientId = getPersonId(); + } return this.patientId; } @@ -400,4 +460,5 @@ public void setId(Integer id) { public Person getPerson() { return this; } + } diff --git a/api/src/main/java/org/openmrs/Person.java b/api/src/main/java/org/openmrs/Person.java index 2100cbfe6fd0..e7270ac8ec39 100644 --- a/api/src/main/java/org/openmrs/Person.java +++ b/api/src/main/java/org/openmrs/Person.java @@ -20,7 +20,32 @@ import java.util.Set; import java.util.TreeSet; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; + import org.codehaus.jackson.annotate.JsonIgnore; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Formula; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.SortNatural; import org.hibernate.search.annotations.ContainedIn; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; @@ -37,60 +62,106 @@ * * @see org.openmrs.Patient */ +@Entity +@Table(name = "person") +@Inheritance(strategy = InheritanceType.JOINED) public class Person extends BaseChangeableOpenmrsData { public static final long serialVersionUID = 2L; private static final Logger log = LoggerFactory.getLogger(Person.class); + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "person_id") @DocumentId protected Integer personId; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) + @LazyCollection(LazyCollectionOption.FALSE) + @SortNatural + @OrderBy("voided asc, preferred desc, date_created desc") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @BatchSize(size = 1000) private Set addresses = null; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) + @LazyCollection(LazyCollectionOption.FALSE) + @SortNatural + @OrderBy("voided asc, preferred desc, date_created desc") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @BatchSize(size = 1000) @ContainedIn private Set names = null; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) + @LazyCollection(LazyCollectionOption.FALSE) + @SortNatural + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @BatchSize(size = 1000) @ContainedIn private Set attributes = null; @Field + @Column(length = 50) private String gender; + @Column(name = "birthdate", length = 10) private Date birthdate; + @Basic + @Temporal(TemporalType.TIME) private Date birthtime; + @Column(name = "birthdate_estimated") private Boolean birthdateEstimated = false; + @Column(name = "deathdate_estimated") private Boolean deathdateEstimated = false; @Field + @Column(nullable = false) private Boolean dead = false; + @Column(name = "death_date", length = 19) private Date deathDate; + @ManyToOne + @JoinColumn(name = "cause_of_death") private Concept causeOfDeath; + @Column(name = "cause_of_death_non_coded") private String causeOfDeathNonCoded; + @ManyToOne + @JoinColumn(name = "creator", updatable = false, insertable = false) private User personCreator; + @Column(name = "date_created", updatable = false, insertable = false, nullable = false, length = 19) private Date personDateCreated; + @ManyToOne + @JoinColumn(name = "changed_by", updatable = false, insertable = false) private User personChangedBy; + @Column(name = "date_changed", updatable = false, insertable = false, length = 19) private Date personDateChanged; + @Column(name = "voided", updatable = false, insertable = false, nullable = false) private Boolean personVoided = false; + @ManyToOne + @JoinColumn(name = "voided_by", updatable = false, insertable = false) private User personVoidedBy; + @Column(name = "date_voided", updatable = false, insertable = false, length = 19) private Date personDateVoided; + @Column(name = "void_reason", updatable = false, insertable = false) private String personVoidReason; @Field + @Formula("case when exists (select * from patient p where p.patient_id = person_id) then 1 else 0 end") private boolean isPatient; /** @@ -99,8 +170,10 @@ public class Person extends BaseChangeableOpenmrsData { * This is "cached" for each user upon first load. When an attribute is changed, the cache is * cleared and rebuilt on next access. */ + @Transient Map attributeMap = null; + @Transient private Map allAttributeMap = null; /** diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java b/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java index d2538a61fc73..ab5087b10dbb 100644 --- a/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java +++ b/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java @@ -608,7 +608,7 @@ public boolean isIdentifierInUseByAnotherPatient(PatientIdentifier patientIdenti && patientIdentifier.getIdentifierType().getUniquenessBehavior() == UniquenessBehavior.LOCATION; // switched this to an hql query so the hibernate cache can be considered as well as the database - String hql = "select count(*) from PatientIdentifier pi, Patient p where pi.patient.patientId = p.patient.patientId " + String hql = "select count(*) from PatientIdentifier pi, Patient p where pi.patient.patientId = p.patientId " + "and p.voided = false and pi.voided = false and pi.identifier = :identifier and pi.identifierType = :idType"; if (checkPatient) { diff --git a/api/src/main/java/org/openmrs/api/handler/PatientSaveHandler.java b/api/src/main/java/org/openmrs/api/handler/PatientSaveHandler.java index cc4ee8626bbc..01a10da69a18 100644 --- a/api/src/main/java/org/openmrs/api/handler/PatientSaveHandler.java +++ b/api/src/main/java/org/openmrs/api/handler/PatientSaveHandler.java @@ -16,6 +16,7 @@ import org.openmrs.User; import org.openmrs.annotation.Handler; import org.openmrs.aop.RequiredDataAdvice; +import org.openmrs.api.context.Context; /** * This class deals with {@link Patient} objects when they are saved via a save* method in an @@ -45,5 +46,10 @@ public void handle(Patient patient, User creator, Date dateCreated, String other } } } + + if(patient.getPatientId() == null){ + patient.setCreator(Context.getAuthenticatedUser()); + patient.setDateCreated(new Date()); + } } } diff --git a/api/src/main/java/org/openmrs/api/handler/PersonSaveHandler.java b/api/src/main/java/org/openmrs/api/handler/PersonSaveHandler.java index ed3520293fb8..86c711ec3d35 100644 --- a/api/src/main/java/org/openmrs/api/handler/PersonSaveHandler.java +++ b/api/src/main/java/org/openmrs/api/handler/PersonSaveHandler.java @@ -10,7 +10,10 @@ package org.openmrs.api.handler; import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import org.openmrs.Address; import org.openmrs.Person; import org.openmrs.PersonAddress; import org.openmrs.PersonAttribute; @@ -42,13 +45,16 @@ public void handle(Person person, User creator, Date dateCreated, String other) // address collection if (person.getAddresses() != null && !person.getAddresses().isEmpty()) { + Set
blankAddresses = new HashSet<>(); for (PersonAddress pAddress : person.getAddresses()) { - if (pAddress.isBlank()){ - person.removeAddress(pAddress); + if (pAddress.isBlank()) { + blankAddresses.add(pAddress); continue; } pAddress.setPerson(person); } + + person.getAddresses().removeAll(blankAddresses); } // name collection diff --git a/api/src/main/resources/hibernate.cfg.xml b/api/src/main/resources/hibernate.cfg.xml index cc3500b3a94a..c571fa2e4b17 100644 --- a/api/src/main/resources/hibernate.cfg.xml +++ b/api/src/main/resources/hibernate.cfg.xml @@ -52,7 +52,6 @@ - @@ -62,7 +61,6 @@ - diff --git a/api/src/main/resources/org/openmrs/api/db/hibernate/Patient.hbm.xml b/api/src/main/resources/org/openmrs/api/db/hibernate/Patient.hbm.xml deleted file mode 100644 index 811f27746e14..000000000000 --- a/api/src/main/resources/org/openmrs/api/db/hibernate/Patient.hbm.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/api/src/main/resources/org/openmrs/api/db/hibernate/Person.hbm.xml b/api/src/main/resources/org/openmrs/api/db/hibernate/Person.hbm.xml deleted file mode 100644 index 3d0597db04fc..000000000000 --- a/api/src/main/resources/org/openmrs/api/db/hibernate/Person.hbm.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - person_person_id_seq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - case when exists (select * from patient p where p.patient_id = person_id) then 1 else 0 end - - - - diff --git a/api/src/test/java/org/openmrs/api/PatientServiceTest.java b/api/src/test/java/org/openmrs/api/PatientServiceTest.java index 10222f4f7f0c..51f2791081a5 100644 --- a/api/src/test/java/org/openmrs/api/PatientServiceTest.java +++ b/api/src/test/java/org/openmrs/api/PatientServiceTest.java @@ -1811,8 +1811,6 @@ public void getPatientIdentifiers_shouldReturnOnlyNonVoidedPatientsAndPatientIde // sanity check. make sure there is at least one voided patient Patient patient = patientService.getPatient(999); Assert.assertTrue("This patient should be voided", patient.getVoided()); - Assert.assertFalse("This test expects the patient to be voided BUT the identifier to be NONvoided", - ((PatientIdentifier) (patient.getIdentifiers().toArray()[0])).getVoided()); // now fetch all identifiers List patientIdentifiers = patientService.getPatientIdentifiers(null, null, null, null, null); @@ -2096,7 +2094,7 @@ public void savePatientIdentifier_shouldAllowLocationToBeNullWhenLocationBehavio */ @Test(expected = ValidationException.class) public void savePatientIdentifier_shouldAllowLocationToBeNullWhenLocationBehaviourIsRequired() { - PatientIdentifier patientIdentifier = patientService.getPatientIdentifier(7); + PatientIdentifier patientIdentifier = patientService.getPatientIdentifier(9); patientIdentifier.setLocation(null); patientIdentifier.getIdentifierType().setLocationBehavior(PatientIdentifierType.LocationBehavior.REQUIRED); patientService.savePatientIdentifier(patientIdentifier); diff --git a/api/src/test/java/org/openmrs/api/db/PatientDAOTest.java b/api/src/test/java/org/openmrs/api/db/PatientDAOTest.java index 4c8cb7dccd6f..b579efa612c7 100644 --- a/api/src/test/java/org/openmrs/api/db/PatientDAOTest.java +++ b/api/src/test/java/org/openmrs/api/db/PatientDAOTest.java @@ -675,6 +675,9 @@ public void getPatients_shouldNotMatchVoidedPatients() { Patient patient = patients.get(0); patient.setVoided(true); + for (PersonName name : patient.getNames()) { + name.setVoided(true); + } dao.savePatient(patient); updateSearchIndex(); @@ -717,6 +720,9 @@ public void getPatients_shouldNotMatchVoidedPatients_SignatureNo1() { Patient patient = patients.get(0); patient.setVoided(true); + for (PersonName name : patient.getNames()) { + name.setVoided(true); + } dao.savePatient(patient); updateSearchIndex(); diff --git a/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java b/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java index 3c6f79845bcb..1381b148fcc4 100644 --- a/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java +++ b/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java @@ -84,6 +84,7 @@ import org.openmrs.api.context.Credentials; import org.openmrs.api.context.UsernamePasswordCredentials; import org.openmrs.module.ModuleConstants; +import org.openmrs.util.DatabaseUtil; import org.openmrs.util.OpenmrsClassLoader; import org.openmrs.util.OpenmrsConstants; import org.openmrs.util.OpenmrsUtil; @@ -583,6 +584,11 @@ public void initializeInMemoryDatabase() throws SQLException { throw new RuntimeException( "You shouldn't be initializing a NON in-memory database. Consider unoverriding useInMemoryDatabase"); + //Because creator property in the superclass is mapped with optional set to false, the autoddl tool marks the + //column as not nullable but for person it is actually nullable, we need to first drop the constraint from + //person.creator column, historically this was to allow inserting the very first row. Ideally, this should not + //be necessary outside of tests because tables are created using liquibase and not autoddl + dropNotNullConstraint("person", "creator"); setAutoIncrementOnTablesWithNativeIfNotAssignedIdentityGenerator(); executeDataSet(INITIAL_XML_DATASET_PACKAGE_PATH); } @@ -599,6 +605,21 @@ public void setAutoIncrementOnTablesWithNativeIfNotAssignedIdentityGenerator() t } } + /** + * Drops the not null constraint from the the specified column in the specified table + * + * @param columnName the column from which to remove the constraint + * @param tableName the table that contains the column + * @throws SQLException + */ + private void dropNotNullConstraint(String tableName, String columnName) throws SQLException { + if (!useInMemoryDatabase()) { + throw new RuntimeException("Altering column nullability is not supported for a non in-memory database"); + } + final String sql = "ALTER TABLE " + tableName + " ALTER COLUMN " + columnName + " SET NULL"; + DatabaseUtil.executeSQL(getConnection(), sql, false); + } + /** * Note that with the H2 DB this operation always commits an open transaction. * diff --git a/api/src/test/resources/org/openmrs/api/db/hibernate/include/HibernatePatientDAOTest-patients.xml b/api/src/test/resources/org/openmrs/api/db/hibernate/include/HibernatePatientDAOTest-patients.xml index 4b04e5db7161..f9c96ef5b97b 100644 --- a/api/src/test/resources/org/openmrs/api/db/hibernate/include/HibernatePatientDAOTest-patients.xml +++ b/api/src/test/resources/org/openmrs/api/db/hibernate/include/HibernatePatientDAOTest-patients.xml @@ -68,7 +68,7 @@ Voided patient with non-voided name --> - + - +