diff --git a/nirc_ehr/resources/queries/study/arrival.js b/nirc_ehr/resources/queries/study/arrival.js index 501da5dc..0bf813bc 100644 --- a/nirc_ehr/resources/queries/study/arrival.js +++ b/nirc_ehr/resources/queries/study/arrival.js @@ -24,41 +24,21 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even helper.registerArrival(row.Id, row.date); - //Insert or update demographic and birth records - if (!row.rearrival && !helper.isETL() && !helper.isGeneratedByServer() && !helper.isValidateOnly()) { - - // this allows demographic records in qcstates other than completed - var extraDemographicsFieldMappings = { - 'taskid': row.taskid, - 'qcstate': helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId() - } - - // null (not undefined) required for call to java trigger helper - row.dam = row['Id/demographics/dam'] || null; - row.sire = row['Id/demographics/sire'] || null; - row.species = row['Id/demographics/species'] || null; - row.birth = row['Id/demographics/birth'] || null; - row.gender = row['Id/demographics/gender'] || null; - row.geographic_origin = row['Id/demographics/geographic_origin'] || null; - row.source = row.sourceFacility || null; + // Project, protocol and housing records are created for rearrivals too: the animal's departure closed the previous ones. + if (!helper.isETL() && !helper.isGeneratedByServer() && !helper.isValidateOnly()) { if (row.QCStateLabel) { row.qcstate = helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId(); } - if (row.birth) { - var birthInfo = { + // A rearrival reuses the existing demographics record, so its status is written directly; drafts get the same interim status an arrival draft does. + // The update refreshes the server-side animal cache, so the records opened below validate against this status rather than the departure's. + if (row.rearrival && row.Id && row.QCStateLabel) { + row.calculated_status = (row.QCStateLabel.toUpperCase() === 'IN PROGRESS' || row.QCStateLabel.toUpperCase() === 'REVIEW REQUIRED') ? 'Alive - In Progress' : 'Alive'; + helper.getJavaHelper().updateDemographicsRecord([{ Id: row.Id, - date: row.birth, - qcstate: row.qcstate, - taskid: row.taskid, - performedby: row.performedby - } - - var birthErrors = triggerHelper.saveBirthRecord(row.Id, birthInfo); - if (birthErrors){ - EHR.Server.Utils.addError(scriptErrors, 'birth', birthErrors, 'ERROR'); - } + calculated_status: row.calculated_status + }]); } if (row.Id && row.date) { @@ -100,6 +80,40 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even EHR.Server.Utils.addError(scriptErrors, 'Id', housingErrors, 'ERROR'); } } + } + + //Insert or update demographic and birth records + if (!row.rearrival && !helper.isETL() && !helper.isGeneratedByServer() && !helper.isValidateOnly()) { + + // this allows demographic records in qcstates other than completed + var extraDemographicsFieldMappings = { + 'taskid': row.taskid, + 'qcstate': helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId() + } + + // null (not undefined) required for call to java trigger helper + row.dam = row['Id/demographics/dam'] || null; + row.sire = row['Id/demographics/sire'] || null; + row.species = row['Id/demographics/species'] || null; + row.birth = row['Id/demographics/birth'] || null; + row.gender = row['Id/demographics/gender'] || null; + row.geographic_origin = row['Id/demographics/geographic_origin'] || null; + row.source = row.sourceFacility || null; + + if (row.birth) { + var birthInfo = { + Id: row.Id, + date: row.birth, + qcstate: row.qcstate, + taskid: row.taskid, + performedby: row.performedby + } + + var birthErrors = triggerHelper.saveBirthRecord(row.Id, birthInfo); + if (birthErrors){ + EHR.Server.Utils.addError(scriptErrors, 'birth', birthErrors, 'ERROR'); + } + } row.calculated_status = (row.QCStateLabel.toUpperCase() === 'IN PROGRESS' || row.QCStateLabel.toUpperCase() === 'REVIEW REQUIRED') ? 'Alive - In Progress' : 'Alive'; diff --git a/nirc_ehr/resources/web/nirc_ehr/model/sources/Rearrival.js b/nirc_ehr/resources/web/nirc_ehr/model/sources/Rearrival.js index 059847ea..94ae9c4c 100644 --- a/nirc_ehr/resources/web/nirc_ehr/model/sources/Rearrival.js +++ b/nirc_ehr/resources/web/nirc_ehr/model/sources/Rearrival.js @@ -30,11 +30,14 @@ EHR.model.DataModelManager.registerMetadata('Rearrival', { }, }, acquisitionType: { - allowBlank: false, - columnConfig: { - fixed: true, - width: 150 - }, + allowBlank: true, + hidden: true, + showInGrid: false + }, + CITES: { + allowBlank: true, + hidden: true, + showInGrid: false }, arrivalType: { allowBlank: false, @@ -43,20 +46,40 @@ EHR.model.DataModelManager.registerMetadata('Rearrival', { } }, 'cage': { - allowBlank: true, - hidden: true, - showInGrid: false + allowBlank: false, + columnConfig: { + fixed: true, + width: 200 + }, }, project: { - allowBlank: true, - hidden: true, - showInGrid: false + xtype: 'combo', + columnConfig: { + width: 150 + }, + lookup: { + schemaName: 'ehr', + queryName: 'project', + keyColumn: 'project', + columns: 'project,name', + filterArray: [ + LABKEY.Filter.create('isActive', true, LABKEY.Filter.Types.EQUAL), + ] + }, + allowBlank: false }, arrivalProtocol: { - allowBlank: true, - hidden: true, - showInGrid: false + allowBlank: false, + columnConfig: { + width: 200 + }, + lookup: { + schemaName: 'ehr', + queryName: 'activeProtocols', + keyColumn: 'protocol', + columns: 'protocol,title' + }, }, } } -}); \ No newline at end of file +}); diff --git a/nirc_ehr/test/src/org.labkey.test/tests.nirc_ehr/NIRC_EHRTest.java b/nirc_ehr/test/src/org.labkey.test/tests.nirc_ehr/NIRC_EHRTest.java index 59fe6a13..95eb459a 100644 --- a/nirc_ehr/test/src/org.labkey.test/tests.nirc_ehr/NIRC_EHRTest.java +++ b/nirc_ehr/test/src/org.labkey.test/tests.nirc_ehr/NIRC_EHRTest.java @@ -645,6 +645,74 @@ public void testArrivalForm() verifyOrchardFileGenerated(arrivedAnimal); } + @Test + public void testRearrivalForm() throws Exception + { + String rearrivedAnimal = getExpectedAnimalIDCasing("R7373"); + LocalDateTime now = LocalDateTime.now(); + + goToEHRFolder(); + + log("Seeding an animal that has left the center"); + for (String query : List.of("arrival", "departure", "assignment", "protocolAssignment", "housing", "demographics")) + getApiHelper().deleteAllRecords("study", query, new Filter("Id", rearrivedAnimal)); + + // The departed status is seeded straight onto demographics because a departure does not recalculate + // calculated_status in this module, so inserting one alone would leave the animal alive. + String[] demographicsFields = {"Id", "Species", "Birth", "Gender", "date", "calculated_status", "objectid", "performedby"}; + Object[][] demographicsData = {{rearrivedAnimal, "Rhesus", now.minusDays(30).toString(), getMale(), new Date(), "Shipped", UUID.randomUUID().toString(), 1004}}; + getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), getApiHelper().prepareInsertCommand("study", "demographics", "lsid", demographicsFields, demographicsData), getExtraContext()); + + InsertRowsCommand departure = new InsertRowsCommand("study", "departure"); + departure.addRow(Map.of("Id", rearrivedAnimal, "date", now.minusDays(2), "destination", "Oregon NPRC", "performedby", 1004)); + departure.execute(getApiHelper().getConnection(), getContainerPath()); + + gotoEnterData(); + waitAndClickAndWait(Locator.linkWithText("Rearrivals")); + lockForm(); + + Ext4GridRef rearrivals = _helper.getExt4GridForFormSection("Rearrivals"); + _helper.addRecordToGrid(rearrivals); + + log("A rearrival collects project, protocol and location, and does not collect acquisition type or CITES"); + Assert.assertTrue("Project should be on the Rearrival form", rearrivals.isColumnPresent("project", true)); + Assert.assertTrue("Protocol should be on the Rearrival form", rearrivals.isColumnPresent("arrivalProtocol", true)); + Assert.assertTrue("Location should be on the Rearrival form", rearrivals.isColumnPresent("cage", true)); + Assert.assertFalse("Acquisition type should not be on the Rearrival form", rearrivals.isColumnPresent("acquisitionType", true)); + Assert.assertFalse("CITES should not be on the Rearrival form", rearrivals.isColumnPresent("CITES", true)); + + rearrivals.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING))); + rearrivals.setGridCell(1, "arrivalType", "Non-quarantine Arrival"); + rearrivals.setGridCell(1, "Id", rearrivedAnimal); + rearrivals.setGridCell(1, "cage", "C1"); + rearrivals.setGridCell(1, "project", "640991"); + rearrivals.setGridCell(1, "arrivalProtocol", "dummyprotocol"); + rearrivals.setGridCell(1, "sourceFacility", "BIOQUAL, Inc."); + submitForm("Submit Final", "Finalize"); + + goToSchemaBrowser(); + DataRegionTable table = viewQueryData("study", "arrival"); + table.setFilter("Id", "Equals", rearrivedAnimal); + CustomizeView view = table.openCustomizeGrid(); + view.addColumn("cage"); + view.addColumn("project"); + view.addColumn("arrivalProtocol"); + view.applyCustomView(); + Assert.assertEquals("Invalid Rearrival record", Arrays.asList("C1"), table.getRowDataAsText(0, "cage")); + Assert.assertEquals("Invalid Rearrival record", Arrays.asList("640991"), table.getRowDataAsText(0, "project")); + Assert.assertEquals("Invalid Rearrival record", Arrays.asList("dummyprotocol"), table.getRowDataAsText(0, "arrivalProtocol")); + + log("The rearrival opens the assignment, protocol assignment and housing records that the departure closed"); + verifyRowCreated("study", "assignment", rearrivedAnimal, 1); + verifyRowCreated("study", "protocolAssignment", rearrivedAnimal, 1); + verifyRowCreated("study", "housing", rearrivedAnimal, 1); + + log("The rearrived animal is alive again"); + List> rows = executeSelectRowCommand("study", "demographics", ContainerFilter.Current, "/" + getContainerPath(), List.of(new Filter("Id", rearrivedAnimal))).getRows(); + assertEquals("Expected one demographics record for the rearrived animal", 1, rows.size()); + assertEquals("Rearrived animal should be alive", "Alive", rows.getFirst().get("calculated_status")); + } + @Test public void testBirthForm() {