diff --git a/nbri_ehr/resources/data/editable_lookups.tsv b/nbri_ehr/resources/data/editable_lookups.tsv
index edde79b..c12dbd0 100644
--- a/nbri_ehr/resources/data/editable_lookups.tsv
+++ b/nbri_ehr/resources/data/editable_lookups.tsv
@@ -10,6 +10,7 @@ ehr_lookups alopecia_type Behavior Alopecia Type Behavior observation fixed valu
ehr_lookups alopecia_regrowth Behavior Alopecia Regrowth Behavior observation fixed values.
ehr_lookups amount_units Clinical Amount Units "Used in drugs, treatments given and treatments ordered datasets."
ehr_lookups app_score Behavior Appetite Score Behavior observation fixed values.
+ehr_lookups areas Housing Areas Top level of the housing location hierarchy.
ehr_lookups arrival_type Colony Management Arrival Type Types of arrivals.
ehr_lookups arthritis_types Clinical Arthritis Types Clinical observation fixed values.
ehr_lookups att_score Behavior Attitude Score Behavior observation fixed values.
@@ -23,6 +24,8 @@ ehr_lookups blood_draw_tube_type Clinical Blood Draw Tube Type Used in blood dra
ehr_lookups blood_sample_type Clinical Blood Sample Types Used in blood draw datasets.
ehr_lookups blood_tube_volumes Clinical Blood Tube Volumes Used in blood draw datasets.
ehr_lookups breeding_type Colony Management Breeding Type Breeding group assignment codes.
+ehr_lookups buildings Housing Buildings Buildings within an area.
+ehr_lookups cage Housing Cages Cage locations within a room.
ehr_lookups cage_type Colony Management Cage Type Used in cage details.
ehr_lookups calculated_status_codes Colony Management Calculated Status Animal status values.
ehr_lookups capillary_refill_time Clinical Capillary Refill Times Used clinical observations.
@@ -58,6 +61,7 @@ ehr_lookups fecal_smear_score Clinical Fecal Smear Score Clinical observation fi
ehr_lookups feed_assess_types Behavior Fecal Assessment Types Behavior observation fixed values.
ehr_lookups flag_categories Colony Management Flag Categories Used to specify flag behavior.
ehr_lookups flag_values Colony Management Flag Values Animal flag values.
+ehr_lookups floors Housing Floors Floors within a building.
ehr_lookups gastro_types Clinical Gastrointestinal Types Clinical observation fixed values.
ehr_lookups gender_codes Colony Management Sex Codes Used in demographics dataset.
ehr_lookups general_obs Clinical General Observation Types Clinical observation types.
@@ -69,6 +73,7 @@ ehr_lookups housing_reason Housing Housing Move Reason Used in housing dataset f
ehr_lookups hyd_score Clinical Hydration Score Clinical observation fixed values.
ehr_lookups incision_score Clinical Incision Status Clinical observation fixed values.
ehr_lookups indoor_outdoor Colony Management Indoor/Outdoor Cage details
+ehr investigators Colony Management Investigators Investigators available for protocol and project assignment.
ehr_lookups keyword Clinical Keyword Clinical observation fixed values.
ehr_lookups lameness Clinical Lameness Clinical observation fixed values.
ehr_lookups mass_type Clinical Mass Type Clinical observation fixed values.
@@ -102,6 +107,8 @@ ehr_lookups procedure_category Clinical Procedure Category Used in procedures to
ehr_lookups procedures Clinical Procedures List of animal procedures.F
ehr_lookups problem_list_category Clinical Problem List Category Used in problem list dataset. Set when opening a case.
ehr_lookups problem_list_subcategory Clinical Problem List Subcategory Used in problem list to categorize problems.
+ehr project Colony Management Projects Projects animals can be assigned to.
+ehr protocol Colony Management Protocols IACUC protocols animals can be assigned to.
ehr_lookups protocol_category Colony Management Protocol Category
ehr_lookups protocol_state Colony Management Protocol State
ehr_lookups protocol_type Colony Management Protocol Type
@@ -112,6 +119,7 @@ ehr_lookups regulatory_stress_levels Colony Management Regulatory Stress Levels
ehr_lookups req_order_state Colony Management Request Order State
ehr_lookups req_order_type Colony Management Request Order Type
ehr_lookups respiratory_observations Clinical Respiratory Observations Used in clinical observations.
+ehr_lookups rooms Housing Rooms Rooms within a building and floor.
ehr_lookups routes Clinical Treatment Routes Used in drug and treatment datasets.
ehr_lookups sib_score Behavior SIB Score Behavior observation fixed values.
ehr_lookups skin_problem Clinical Skin Problems Clinical observation fixed values.
diff --git a/nbri_ehr/resources/data/source.tsv b/nbri_ehr/resources/data/source.tsv
index 5c3f18a..a62d9eb 100644
--- a/nbri_ehr/resources/data/source.tsv
+++ b/nbri_ehr/resources/data/source.tsv
@@ -1,72 +1,155 @@
code meaning
+ACCULAB Acculab Life Sciences
+ABL Advanced Bioscience Laboratories
+AECOM Albert Einstein College of Medicine
+ALLERGAN Allergan, Inc
+ALPHAGEN Alpha Genesis
+ASU Arizona State University
+AFRRI Armed Forces Radiobiological Res.
AAI Asiatic Animal Imports
+BATTELLE Battelle Research
+BCM Baylor College of Medicine
+BEDFORD-VA Bedford VA Hospital
BIOQUAL Bioqual, Incorporated
+BIOTOX Biotox Sciences
+BLACKPOOL Blackpool Zoological Gardens
+BOBCROW Bob Crow (USDA 58B142)
+BOSTONU Boston University
BRANDEIS Brandeis University
+BWH Brigham and Women's Hospital
+BRISTOL Bristol-Myers Company
+BRONX-VA Bronx VA Hospital
+BWEF BWEF-Primegen
CA-DPH Calif Dept Public Health Facilities
+CIMR Calif. Institute for Medical Research
+CALTECH California Institute of Technology
CA-DOH California State Department of Health
CPRC Carribean Primate Center
CWRU Case Western Reserve University
+CDC-GL Center for Disease Control - Garcia.Lema
UCD-CNS Center for Neuroscience, UCD
CR-KL Charles River - Key Lois
CRL Charles River Labs
CRRP Charles River Research Primates Inc
BROOKFIELD Chicago Zoological Park (Brookfield Zoo)
CHILDRENS Childrens Hospital
+CHOP Childrens Hospital of Philadelphia
CHIMR Christ Hospital Inst. for Medical Res.
+CMC Colorado Mountain College
CSU Colorado State University
+COLUMBIA Columbia University
+CONNAUGHT Connaught Laboratories
CORNELL Cornell University
COULSTON Coulston Foundation
COVANCE Covance Research Products, Inc.
PRIMGEN CSP-Primgen
CUTTER Cutter Lab
+DHMRI David H. Murdock Res Institute
DMT Del Mundo Trading
+DENVER-ZOO Denver Zoological Gardens
+DODGE-ZOO Dodge City Zoo
+DOHENY Doheny Eye Foundation
+DOWNSTATE Downstate Medical Center
+DUKE Duke University Medical Center
EPZ El Paso Zoological Gardens
ENVIGO Envigo
+FOLSOM-ZOO Folsom Zoo
+GENENTECH Genentech, Inc.
HAHNEMANN Hahnemann University
HL Hazelton Laboratories
HLA Hazelton Laboratories America Inc.
+HEMKER-ZOO Hemker Park and Zoo
+INDY-ZOO Indianapolis Zoo
UNK Institution Unknown
ISU-VDL Iowa State Univ, Vet Diagnostic Lab
+ISIS Isis Services
JVL Jan Vacek Limited
JHMC Jewish Hospital & Medical Center
+JHU Johns Hopkins University
JRI Johnson Research Institute
KNLPC Kunming National Laboratory Primate Ctr.
LABS Laboratory Animal Breeders and Services
LABSINDO Labsindo
+LEGACY Legacy Research Institute
LEMSIP LEMSIP, New York Medical Center, RDI
LAIR Letterman Army Res Inst-Presidio
LB Litton Bionetics
LLU Loma Linda University
+LSU-SOM Louisiana State Univ School of Medicine
+MADIGAN Madigan Army Center
+MWRI Magee Womens Research Institute
+MIT Massachusetts Institute of Technology
+MCW-ARC Med College of Wisconsin, Anim Res Ctr
+MCG Medical College of Georgia
+MEDRI Medical Research Institute
+METRO-ZOO Metro Richmond Zoo
MSU-MI Michigan State University
+MICKE-ZOO Micke Grove Zoo
+MPLS-VA Minneapolis Veterans Hospital
+MONKEYJNGL Monkey Jungle
MSU-MT Montana State University
+MONTG-ZOO Montgomery Zoo
+MOORPRK-ZO Moorpark Zoo
+MSSM Mount Sinai School of Medicine
MPI MPI Research
NASA-ARC NASA-Ames Research Center
+NIH National Institutes of Health
NAMRL Naval Aerospace Medical Research Lab
+NEURALINK Neuralink
+NSI Neurosciences Institute
NEPRC New England Primate Research Center
+NMSU-PRI New Mex State Univ, Primate Research Inst
+NICHOLS Nichols Exotics
NIHAC NIH Animal Center
+NEU Northeastern University
+NWU Northwestern University
+OAKLAND-ZO Oakland Zoo
+OSU Ohio State University
+OHSU Oregon Health Sciences University
+OPR Oregon Primate Rescue
ORPRC Oregon Regional Primate Research Center
+OSAGE Osage Research Primates
+PALOALTO-V Palo Alto Veteran's Administration
PARC-SAF Parc Safari African
+PENN-ZOO Pennington Zoo
PPC Perrine Primate Center
PPP Peruvian Primatological Project
PET-FARM Pet Farm
+PTDEF-ZOO Point Defiance Zoo
PRIMLAB Primate Laboratory
PPI Primate Products, Incorporated
+PRINCETON Princeton University
PRIVATE Private Party
RIEPT Res. Inst. of Exp. Pathology & Therapy
+SCRIPPS Research Institute of Scripps Clinic
+RTI Research Triangle Institute
+ROCKEFELLR Rockefeller University
SALK Salk Institute
SFGH San Francisco General Hospital
+SFMC San Francisco Medical Center
+SANTANA-ZO Santa Ana Zoo
+SCHERING Schering, Inc.
+SCIENCEXYZ Science XYZ
+SHARP-CAB Sharp Cabrillo Hospital
SNBL Shin Nippon Biomedical Laboratories
SICONBREC Siconbrec
SBM Sierra Biomedical
+SINCLAIR Sinclair Research Center, LLC
SKB Smith, Kline, Beecham
+SKERI Smith-Kettlewell Eye Research Institute
+SDPC Sonora Desert Primate Conservancy
+STPO South Texas Primate Observatory
SIU Southern Illinois University
SIU-SOM Southern Illinois University Med School
SORI Southern Research Institute
SFRE Southwest Foundation for Res. & Educ.
+SNPRC Southwest Nat. Primate Center
SWRF Southwest Research Foundation
SWRI Southwest Research Institute
SOPF Stanford Outdoor Primate Facility
SRI Stanford Research Institute
+SU-ARF Stanford University Animal Res. Facility
+SUNY-B State Univ of New York at Buffalo
SUNY-SB State Univ of New York at Stony Brook
SXZ Suzhou Xishan Zhongke Lab Animal Co.
SYNTEX Syntex (USA) Incorporated
@@ -74,6 +157,7 @@ TARPON Tarpon Zoo
TTUHSC Texas Tech Health Science Center
BROOKS-AFB Texas, Brooks Air Force Base
TPI The Parkinson's Institute
+TJU Thomas Jefferson University
TNPRC Tulane (Delta) Regional Primate Res Ctr
UCB UC Berkeley
UCLA UC Los Angeles
@@ -81,23 +165,61 @@ UCR UC Riverside
UCSD UC San Diego
UCSF UC San Francisco Vivarium
UC-ARS Univ of Calif, Animal Resources Service
+UC-DBS Univ of Calif, Dept Biological Sci.
GPC Univ of Gottingen Primate Center
UH-RAF Univ of Hawaii, Research Animal Facility
+UMASS-MS Univ of Massachusetts, Med School
OUHSC Univ of Oklahoma Health Sci. Center
+UTMB-G Univ of Texas Medical Branch, Galveston
UTHSC-H Univ of Texas, Health Sci Ctr, Houston
+UTHSC Univ. Texas Health Science Center
+UMDNJ University Med/Dental of New Jersey
+UAB University of Alabama, Birmingham
+UAZ University of Arizona
+UBC University of British Columbia
+UCI University of California, Irvine
+UCHICAGO University of Chicago
UCHSC University of Colorado Health Sciences
UCMC University of Colorado Medical Center
+UFL University of Florida
+UGA University of Georgia
UIC University of Illinois at Chicago
+UIOWA-OAK University of Iowa, Oakdale Facility
+KUMC University of Kansas Medical Center
+ULOUISVL University of Louisville
+UMD University of Maryland
+UMIAMI University of Miami
+UMICH University of Michigan
+UMN University of Minnesota
+UMMC University of Mississippi Medical Center
+MIZZOU University of Missouri, Columbia
+UNMC University of Nebraska Medical Center
UNV University of Nevada
UNM University of New Mexico
+UOREGON University of Oregon
+UPENN University of Pennsylvania
+PITT-DLAR University of Pittsburgh Lab Animal Rscs
+PITT-SOM University of Pittsburgh Medical School
UPR University of Puerto Rico
+USC University of Southern California
+UTENN University of Tennessee
UTSCC University of Texas System Cancer Center
+UTEP University of Texas, El Paso
+UUMC University of Utah Medical Center
UNK-CN Unknown Institution, China
+USC-LA USC Livingston Annex
+VA V.A. Hospital
VBS Valley Biosystems
+VANDERBILT Vanderbilt University
+VCU Virginia Commonwealth University
WFSM Wake Forest School of Medicine
WANPRC Washington Regional Primate Research Ctr
+WUSTL Washington University School of Medicine
+WHITTAKER Whittaker Bioproducts
WHMC Wilford Hall Medical Center
WRPRC Wisconsin Regional Primate Research Ctr
WAI Woodward Asiatic Imports
WWP World Wide Primates, Inc.
+YALE Yale University
YPRC Yemassee Primate Research Center
+YRPRC Yerkes Regional Primate Research Ctr
diff --git a/nbri_ehr/resources/data/species.tsv b/nbri_ehr/resources/data/species.tsv
index 12d3118..754461b 100644
--- a/nbri_ehr/resources/data/species.tsv
+++ b/nbri_ehr/resources/data/species.tsv
@@ -2,12 +2,12 @@ Common Scientific Name Id Prefix Mhc Prefix Max Blood Draw (mL/kg) Max Percent B
Rhesus Macaque Macaca Mulatta 3.0000 1.0000 7.0000
Pig-Tailed Macaque Macaca Nemestrina 3.0000 1.0000 7.0000
Bonnet Macaque Macaca Radiata 3.0000 1.0000 7.0000
-Olive Baboon Papio Anubis 3.0000 1.0000 7.0000
+Olive Baboon Papio Cynocephalus Anubis 3.0000 1.0000 7.0000
Yellow Baboon Papio Cynocephalus 3.0000 1.0000 7.0000
Squirrel Monkey Saimiri Sciureus 3.0000 1.0000 7.0000
African Green Monkey Chlorocebus Aethiops 3.0000 1.0000 7.0000
Domestic Dog Canis Familiaris 3.0000 1.0000 7.0000
-Formosan Rock Macaque Macaca Cyclopis 3.0000 1.0000 7.0000
+Cynomolgus / Crab-Eating Macaque Macaca Fascicularis 3.0000 1.0000 7.0000
Japanese Macaque Macaca Fuscata 3.0000 1.0000 7.000
Talapoin Monkey Cercopithecus Talapoin 3.0000 1.0000 7.0000
Sykes' Monkey Cercopithecus M Albogulari 3.0000 1.0000 7.0000
diff --git a/nbri_ehr/resources/data/species_codes.tsv b/nbri_ehr/resources/data/species_codes.tsv
index feb4100..a914993 100644
--- a/nbri_ehr/resources/data/species_codes.tsv
+++ b/nbri_ehr/resources/data/species_codes.tsv
@@ -2,12 +2,12 @@ Code Scientific Name Common Name Description Genus Species Date Disabled
MMU Macaca Mulatta Rhesus Macaque
MNE Macaca Nemestrina Pig-Tailed Macaque
MRA Macaca Radiata Bonnet Macaque
-PAN Papio Anubis Olive Baboon
+PAN Papio Cynocephalus Anubis Olive Baboon
PCY Papio Cynocephalus Yellow Baboon
SSC Saimiri Sciureus Squirrel Monkey
CAE Chlorocebus Aethiops African Green / Vervet
DOG Canis Familiaris Domestic Dog
-MCY Macaca Cyclopis Formosan Rock Macaque
+MCY Macaca Fascicularis Cynomolgus / Crab-Eating Macaque
MFU Macaca Fuscata Japanese Macaque
CTA Cercopithecus Talapoin Talapoin Monkey
CMA Cercopithecus M Albogulari Sykes' Monkey
diff --git a/nbri_ehr/resources/domain-templates/ehr_lookups.template.xml b/nbri_ehr/resources/domain-templates/ehr_lookups.template.xml
index 36eb0f9..008fafb 100644
--- a/nbri_ehr/resources/domain-templates/ehr_lookups.template.xml
+++ b/nbri_ehr/resources/domain-templates/ehr_lookups.template.xml
@@ -3,6 +3,9 @@
xmlns:dat="http://labkey.org/data/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
Buildings
@@ -10,6 +13,20 @@
string
+
+ dateTime
+
+
+
+
+
+
+
+ Floors
+
+
+ dateTime
+
@@ -35,6 +52,9 @@
string
+
+ dateTime
+
diff --git a/nbri_ehr/resources/queries/ehr_lookups/buildings.query.xml b/nbri_ehr/resources/queries/ehr_lookups/buildings.query.xml
index 90dd3ea..df82540 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/buildings.query.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/buildings.query.xml
@@ -16,6 +16,12 @@
areas
area
description
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml b/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml
index 3d15c46..c3b3060 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml
@@ -13,6 +13,12 @@
ehr_lookups
rooms
room
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/ehr_lookups/floors.query.xml b/nbri_ehr/resources/queries/ehr_lookups/floors.query.xml
index ba3ff03..cc667cc 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/floors.query.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/floors.query.xml
@@ -13,6 +13,12 @@
buildings
name
description
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml b/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml
index 0b522c5..a4030d9 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml
@@ -6,7 +6,27 @@
Rooms
-
+
+
+ false
+ true
+ true
+ true
+ true
+
+ ehr_lookups
+ buildings
+ name
+ description
+
+
+
+
+
+
+
+
/nbri_ehr/cageDetails.view?room=${room}
diff --git a/nbri_ehr/resources/queries/study/arrival.js b/nbri_ehr/resources/queries/study/arrival.js
index 8ee71bb..d374944 100644
--- a/nbri_ehr/resources/queries/study/arrival.js
+++ b/nbri_ehr/resources/queries/study/arrival.js
@@ -8,6 +8,27 @@ require("ehr/triggers").initScript(this);
var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
var idsToSync = [];
+// opens one assignment record against the animal being entered; each dataset carries the assignment under its own field
+function createAssignment(scriptErrors, dataset, fieldName, value, row) {
+ if (!value)
+ return;
+
+ var assignmentRec = {
+ Id: row.Id,
+ date: row.date,
+ taskid: row.taskid,
+ remark: row.remark,
+ qcstate: row.qcstate,
+ performedby: row.performedby
+ };
+ assignmentRec[fieldName] = value;
+
+ var error = triggerHelper.createAssignmentRecord(dataset, row.Id, assignmentRec);
+ if (error) {
+ EHR.Server.Utils.addError(scriptErrors, 'Id', error, 'ERROR');
+ }
+}
+
EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'Arrival', function(event, helper){
// the script scope can outlive a single save, so never inherit ids from a prior one
@@ -68,26 +89,11 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even
}
}
+ // an animal arrives already assigned to a project, a protocol and a group, all entered on the arrival row
if (row.Id && row.date) {
-
- let assignmentRec = {
- Id: row.Id,
- date: row.date,
- taskid: row.taskid,
- remark: row.remark,
- qcstate: row.qcstate,
- performedby: row.performedby
- }
-
- if (row.project) {
- assignmentRec['project'] = row.project;
- triggerHelper.createAssignmentRecord("assignment", row.Id, assignmentRec);
- }
-
- if (row.arrivalProtocol) {
- assignmentRec['protocol'] = row.arrivalProtocol;
- triggerHelper.createAssignmentRecord("protocolAssignment", row.Id, assignmentRec);
- }
+ createAssignment(scriptErrors, 'assignment', 'project', row.project, row);
+ createAssignment(scriptErrors, 'protocolAssignment', 'protocol', row.arrivalProtocol, row);
+ createAssignment(scriptErrors, 'animal_group_members', 'groupId', row.groupId, row);
}
// if 'cage', labeled as "Initial Location" is provided, then insert into housing.
diff --git a/nbri_ehr/resources/queries/study/arrival.query.xml b/nbri_ehr/resources/queries/study/arrival.query.xml
index 0955496..84e30cc 100644
--- a/nbri_ehr/resources/queries/study/arrival.query.xml
+++ b/nbri_ehr/resources/queries/study/arrival.query.xml
@@ -32,7 +32,6 @@
true
- 80
Initial Location
true
true
@@ -40,7 +39,6 @@
ehr_lookups
cage
location
- cage
@@ -61,6 +59,15 @@
title
+
+ Group
+
+ ehr_lookups
+ breeding_type
+ value
+ title
+
+
Source Facility
diff --git a/nbri_ehr/resources/queries/study/birth.js b/nbri_ehr/resources/queries/study/birth.js
index b5a34c6..6531bc9 100644
--- a/nbri_ehr/resources/queries/study/birth.js
+++ b/nbri_ehr/resources/queries/study/birth.js
@@ -9,6 +9,31 @@ EHR.Server.Utils = require("ehr/utils").EHR.Server.Utils;
var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
var idsToSync = [];
+// conception ids claimed by the rows of this save that have already been validated. Rows entered together are not in
+// study.birth yet when each one is checked, so this is the only way the one-birth-per-conception rule can see them.
+var conceptIdsInSave = [];
+
+// opens one assignment record against the animal being entered; each dataset carries the assignment under its own field
+function createAssignment(scriptErrors, dataset, fieldName, value, row) {
+ if (!value)
+ return;
+
+ var assignmentRec = {
+ Id: row.Id,
+ date: row.date,
+ taskid: row.taskid,
+ remark: row.remark,
+ qcstate: row.qcstate,
+ performedby: row.performedby
+ };
+ assignmentRec[fieldName] = value;
+
+ var error = triggerHelper.createAssignmentRecord(dataset, row.Id, assignmentRec);
+ if (error) {
+ EHR.Server.Utils.addError(scriptErrors, 'Id', error, 'ERROR');
+ }
+}
+
function onInit(event, helper){
helper.setScriptOptions({
allowAnyId: true,
@@ -24,6 +49,7 @@ function onInit(event, helper){
// the script scope can outlive a single save, so never inherit ids from a prior one
idsToSync = [];
+ conceptIdsInSave = [];
helper.decodeExtraContextProperty('birthsInTransaction');
}
@@ -54,12 +80,19 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even
//when updating a record that already carries this conception id, the existing row accounts for one match
var conceptIdThreshold = (oldRow && oldRow.conceptId === row.conceptId) ? 1 : 0;
- if (triggerHelper.totalRecords('study', 'birth', 'conceptId', row.conceptId) > conceptIdThreshold) {
- EHR.Server.Utils.addError(scriptErrors, 'conceptId', 'This conception Id is already used by another birth record', 'WARN');
+ var claimedBySavedRow = triggerHelper.totalRecords('study', 'birth', 'conceptId', row.conceptId) > conceptIdThreshold;
+
+ // rows are validated one at a time and collected below, so this list holds the earlier rows of this save only
+ var claimedByEarlierRow = conceptIdsInSave.indexOf(row.conceptId) > -1;
+
+ if (claimedBySavedRow || claimedByEarlierRow) {
+ EHR.Server.Utils.addError(scriptErrors, 'conceptId', 'This conception Id is already used by another birth record', 'ERROR');
}
+ conceptIdsInSave.push(row.conceptId);
+
if (triggerHelper.totalRecords('study', 'pregnancy', 'conceptId', row.conceptId) > 0) {
- EHR.Server.Utils.addError(scriptErrors, 'conceptId', 'This conception Id is already used by a pregnancy outcome record', 'INFO');
+ EHR.Server.Utils.addError(scriptErrors, 'conceptId', 'This conception Id is already used by a pregnancy outcome record', 'WARN');
}
}
@@ -69,29 +102,14 @@ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Even
row.qcstate = helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId();
}
- if (row.Id && row.date) {
-
- let assignmentRec = {
- Id: row.Id,
- date: row.date,
- taskid: row.taskid,
- remark: row.remark,
- qcstate: row.qcstate,
- performedby: row.performedby
- }
-
- if (row.project) {
- assignmentRec['project'] = row.project;
- triggerHelper.createAssignmentRecord("assignment", row.Id, assignmentRec);
- }
+ if (!helper.isGeneratedByServer() && !helper.isValidateOnly()) {
- if (row.birthProtocol) {
- assignmentRec['protocol'] = row.birthProtocol;
- triggerHelper.createAssignmentRecord("protocolAssignment", row.Id, assignmentRec);
+ // an animal is born already assigned to a project, a protocol and a group, all entered on the birth row
+ if (row.Id && row.date) {
+ createAssignment(scriptErrors, 'assignment', 'project', row.project, row);
+ createAssignment(scriptErrors, 'protocolAssignment', 'protocol', row.birthProtocol, row);
+ createAssignment(scriptErrors, 'animal_group_members', 'groupId', row.groupId, row);
}
- }
-
- if (!helper.isGeneratedByServer() && !helper.isValidateOnly()) {
// if 'cage', labeled as "Birth Location" is provided, then insert into housing.
if (row.cage && row.Id && row.date) {
diff --git a/nbri_ehr/resources/queries/study/birth.query.xml b/nbri_ehr/resources/queries/study/birth.query.xml
index 034b7f0..f424994 100644
--- a/nbri_ehr/resources/queries/study/birth.query.xml
+++ b/nbri_ehr/resources/queries/study/birth.query.xml
@@ -22,14 +22,12 @@
Birth Location
- 80
true
true
ehr_lookups
cage
location
- cage
@@ -60,6 +58,15 @@
title
+
+ Group
+
+ ehr_lookups
+ breeding_type
+ value
+ title
+
+
Breeding Type
diff --git a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml
index 14fbe82..eed9f6a 100644
--- a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml
+++ b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml
@@ -128,6 +128,10 @@
varchar
+
+ Group
+ varchar
+
varchar
@@ -247,6 +251,10 @@
varchar
+
+ Group
+ varchar
+
varchar
diff --git a/nbri_ehr/resources/web/nbri_ehr/field/ConceptionField.js b/nbri_ehr/resources/web/nbri_ehr/field/ConceptionField.js
new file mode 100644
index 0000000..050daa0
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/field/ConceptionField.js
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * The conception Id field on the Births form. The value is never typed: clicking it opens the same window the Start
+ * with Conception button uses, and the conception picked there is copied onto the row that was clicked. It serves as
+ * both the grid's cell editor and the row editor's form field, so it looks for the row it is editing in either place.
+ */
+Ext4.define('NBRI_EHR.field.ConceptionField', {
+ extend: 'Ext.form.field.Trigger',
+ alias: 'widget.nbri_ehr-conceptionField',
+
+ editable: false,
+ triggerCls: 'x4-form-search-trigger',
+
+ initComponent: function(){
+ this.callParent(arguments);
+
+ if (!this.triggerToolTip){
+ this.triggerToolTip = 'Click to pick the conception this birth came from';
+ }
+
+ this.on('render', function(){
+ this.triggerEl.set({'data-qtip': Ext4.htmlEncode(this.triggerToolTip)});
+ }, this);
+ },
+
+ initEvents: function(){
+ this.callParent(arguments);
+
+ if (this.readOnly){
+ return;
+ }
+
+ // the base class only watches the trigger itself, but the field holds nothing that can be typed, so a click
+ // anywhere in it should open the window
+ this.mon(this.inputEl, 'click', this.onTriggerClick, this);
+
+ // as a cell editor the field is created and focused by the same single click that starts editing the cell,
+ // which the grid consumes. Opening on focus as well keeps that one click enough. In the row editor focus
+ // arrives by tabbing through the form, where a window must not open, so this is limited to the editor.
+ if (this.inEditor){
+ this.on('focus', this.onTriggerClick, this);
+ }
+ },
+
+ onTriggerClick: function(){
+ // both the click and the focus can report the same gesture, and the window is modal, so only ever open one
+ if (this.readOnly || this.disabled || this.pickerOpen){
+ return;
+ }
+
+ this.pickerOpen = true;
+
+ var target = this.getTargetRow();
+ if (!target){
+ this.pickerOpen = false;
+ return;
+ }
+
+ var picker = Ext4.create('NBRI_EHR.window.StartWithConceptionWindow', target);
+ picker.on('destroy', function(){
+ this.pickerOpen = false;
+ }, this);
+ picker.show();
+ },
+
+ // as a cell editor the row comes from the grid's editing plugin; in the row editor it is whatever record the form
+ // is bound to. Either way the window is handed the row so it updates it rather than adding a new one.
+ getTargetRow: function(){
+ var grid = this.up('gridpanel');
+ if (grid){
+ var editingPlugin = grid.getPlugin(grid.editingPluginId);
+ var record = editingPlugin ? editingPlugin.getActiveRecord() : null;
+
+ // close the cell editor before the modal window covers the grid; an editor left open would write its
+ // stale value back over the conception copied onto the row
+ if (editingPlugin){
+ editingPlugin.completeEdit();
+ }
+
+ return {
+ targetStore: grid.store,
+ formConfig: grid.formConfig,
+ targetRecord: record
+ };
+ }
+
+ var form = this.up('form');
+ var boundRecord = form ? form.getForm().getRecord() : null;
+ if (!boundRecord){
+ return null;
+ }
+
+ return {
+ targetStore: boundRecord.store,
+ targetRecord: boundRecord
+ };
+ }
+});
diff --git a/nbri_ehr/resources/web/nbri_ehr/model/sources/Arrival.js b/nbri_ehr/resources/web/nbri_ehr/model/sources/Arrival.js
index af0c748..9883d37 100644
--- a/nbri_ehr/resources/web/nbri_ehr/model/sources/Arrival.js
+++ b/nbri_ehr/resources/web/nbri_ehr/model/sources/Arrival.js
@@ -17,13 +17,6 @@ Ext4.onReady(function() {
});
EHR.model.DataModelManager.registerMetadata('Arrival', {
- allQueries: {
- // lowercase to match the key Default.js and Assignment.js use; a differently-cased key shadows theirs entirely
- // rather than merging with it
- 'enddate': {
- hidden: true
- }
- },
byQuery: {
'study.arrival': {
'cage': {
@@ -62,16 +55,53 @@ EHR.model.DataModelManager.registerMetadata('Arrival', {
width: 200
}
},
- // project and protocol are entered through the Project Assignment and Protocol Assignment sections
+ // an animal joins the colony already assigned to a project, a protocol and a group; the trigger script
+ // opens the matching assignment record for each one
project: {
- allowBlank: true,
- hidden: true,
- showInGrid: false
+ xtype: 'combo',
+ allowBlank: false,
+ nullable: false,
+ columnConfig: {
+ fixed: true,
+ width: 150
+ },
+ lookup: {
+ schemaName: 'ehr',
+ queryName: 'project',
+ keyColumn: 'project',
+ columns: 'project,name',
+ filterArray: [
+ LABKEY.Filter.create('isActive', true, LABKEY.Filter.Types.EQUAL),
+ ]
+ }
},
arrivalProtocol: {
- allowBlank: true,
- hidden: true,
- showInGrid: false
+ xtype: 'combo',
+ allowBlank: false,
+ nullable: false,
+ columnConfig: {
+ fixed: true,
+ width: 150
+ },
+ // set displayColumn: ehr.protocol's title column (displayName) is not returned by this query
+ lookup: {
+ schemaName: 'ehr',
+ queryName: 'activeProtocols',
+ keyColumn: 'protocol',
+ displayColumn: 'protocol',
+ columns: 'protocol,title'
+ }
+ },
+ groupId: {
+ allowBlank: false,
+ nullable: false,
+ columnConfig: {
+ width: 200
+ },
+ lookup: {
+ // the shared default filters on a date column that the breeding type lookup does not have
+ filterArray: []
+ }
},
performedby: {
hidden: true,
diff --git a/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js b/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js
index de3c560..bed0ab7 100644
--- a/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js
+++ b/nbri_ehr/resources/web/nbri_ehr/model/sources/Birth.js
@@ -17,13 +17,6 @@ Ext4.onReady(function() {
});
EHR.model.DataModelManager.registerMetadata('Birth', {
- allQueries: {
- // lowercase to match the key Default.js and Assignment.js use; a differently-cased key shadows theirs entirely
- // rather than merging with it
- 'enddate': {
- hidden: true
- }
- },
byQuery: {
'study.birth': {
Id: {
@@ -77,16 +70,51 @@ EHR.model.DataModelManager.registerMetadata('Birth', {
width: 200
},
},
- // project and protocol are entered through the Project Assignment and Protocol Assignment sections
+ // an animal joins the colony already assigned to a project, a protocol and a group; the trigger script
+ // opens the matching assignment record for each one
project: {
- allowBlank: true,
- hidden: true,
- showInGrid: false
+ xtype: 'combo',
+ allowBlank: false,
+ nullable: false,
+ columnConfig: {
+ width: 150
+ },
+ lookup: {
+ schemaName: 'ehr',
+ queryName: 'project',
+ keyColumn: 'project',
+ columns: 'project,name',
+ filterArray: [
+ LABKEY.Filter.create('isActive', true, LABKEY.Filter.Types.EQUAL),
+ ]
+ }
},
birthProtocol: {
- allowBlank: true,
- hidden: true,
- showInGrid: false
+ xtype: 'combo',
+ allowBlank: false,
+ nullable: false,
+ columnConfig: {
+ width: 150
+ },
+ // set displayColumn: ehr.protocol's title column (displayName) is not returned by this query
+ lookup: {
+ schemaName: 'ehr',
+ queryName: 'activeProtocols',
+ keyColumn: 'protocol',
+ displayColumn: 'protocol',
+ columns: 'protocol,title'
+ }
+ },
+ groupId: {
+ allowBlank: false,
+ nullable: false,
+ columnConfig: {
+ width: 200
+ },
+ lookup: {
+ // the shared default filters on a date column that the breeding type lookup does not have
+ filterArray: []
+ }
},
'Id/demographics/birth': {
allowBlank: false
@@ -95,16 +123,14 @@ EHR.model.DataModelManager.registerMetadata('Birth', {
allowBlank: false,
nullable: false
},
- // see the note above on the fields the Start with Conception window populates
+ // the conception is picked rather than typed: clicking the field, in the grid or in the row editor, opens
+ // the same window the Start with Conception button uses and copies that conception onto the row
conceptId: {
+ xtype: 'nbri_ehr-conceptionField',
allowBlank: false,
nullable: false,
columnConfig: {
- width: 150,
- editable: false
- },
- formEditorConfig: {
- readOnly: true
+ width: 150
}
},
breedingType: {
diff --git a/nbri_ehr/resources/web/nbri_ehr/model/sources/NBRIDefault.js b/nbri_ehr/resources/web/nbri_ehr/model/sources/NBRIDefault.js
index ea19076..1bca93c 100644
--- a/nbri_ehr/resources/web/nbri_ehr/model/sources/NBRIDefault.js
+++ b/nbri_ehr/resources/web/nbri_ehr/model/sources/NBRIDefault.js
@@ -86,6 +86,9 @@ EHR.model.DataModelManager.registerMetadata('Default', {
fixed: true,
width: 150
},
+ lookup: {
+ filterArray: [LABKEY.Filter.create('dateDisabled', null, LABKEY.Filter.Types.ISBLANK)]
+ }
},
QCState: {
hidden: true,
diff --git a/nbri_ehr/resources/web/nbri_ehr/model/sources/Rearrival.js b/nbri_ehr/resources/web/nbri_ehr/model/sources/Rearrival.js
index 059847e..c9b83ea 100644
--- a/nbri_ehr/resources/web/nbri_ehr/model/sources/Rearrival.js
+++ b/nbri_ehr/resources/web/nbri_ehr/model/sources/Rearrival.js
@@ -57,6 +57,12 @@ EHR.model.DataModelManager.registerMetadata('Rearrival', {
hidden: true,
showInGrid: false
},
+ // a rearriving animal keeps the assignments it already had; new ones are made on the Assignment forms
+ groupId: {
+ allowBlank: true,
+ hidden: true,
+ showInGrid: false
+ },
}
}
});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js
index bfdb0fd..0ee4d92 100644
--- a/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js
@@ -255,17 +255,13 @@ Ext4.define('NBRI_EHR.panel.SnapshotPanel', {
let paRecords = results.getData()['protocolAssignments'];
let values = [];
- let val;
if (Ext4.isArray(paRecords) && paRecords.length > 0) {
Ext4.each(paRecords, function(record) {
- val = record['protocol/displayName'];
+ let val = LABKEY.Utils.encodeHtml(record['protocol']);
if (record['protocol/InvestigatorId/lastName']) {
- val += " - " + LABKEY.Utils.encodeHtml(record['protocol/InvestigatorId/lastName']);
+ val += ' - ' + LABKEY.Utils.encodeHtml(record['protocol/InvestigatorId/lastName']);
}
- if (record['protocol/InvestigatorId/firstName']) {
- val += ", " + LABKEY.Utils.encodeHtml(record['protocol/InvestigatorId/firstName']);
- }
- values.push(LABKEY.Utils.encodeHtml(val));
+ values.push(val);
});
}
diff --git a/nbri_ehr/resources/web/nbri_ehr/window/StartWithConceptionWindow.js b/nbri_ehr/resources/web/nbri_ehr/window/StartWithConceptionWindow.js
index ebdf711..9c33be5 100644
--- a/nbri_ehr/resources/web/nbri_ehr/window/StartWithConceptionWindow.js
+++ b/nbri_ehr/resources/web/nbri_ehr/window/StartWithConceptionWindow.js
@@ -5,17 +5,23 @@
*/
/**
- * Adds a birth record pre-populated from an existing conception record.
+ * Populates a birth record from an existing conception record.
+ *
+ * With no targetRecord a new birth row is added; with one, only that row's conception fields are replaced and the
+ * rest of the row is left untouched.
*
* @cfg {Object} targetStore
* @cfg {Object} formConfig
+ * @cfg {Object} [targetRecord] the birth row to populate, or null to add a new one
*/
Ext4.define('NBRI_EHR.window.StartWithConceptionWindow', {
extend: 'Ext.window.Window',
initComponent: function(){
+ var isExistingRow = !!this.targetRecord;
+
Ext4.apply(this, {
- title: 'Start with Conception',
+ title: isExistingRow ? 'Change Conception' : 'Start with Conception',
modal: true,
closeAction: 'destroy',
border: true,
@@ -26,12 +32,15 @@ Ext4.define('NBRI_EHR.window.StartWithConceptionWindow', {
width: 370
},
items: [{
- html: 'Select a conception record. A new birth record will be added using the conception Id, along with the dam, sire and species from that conception.',
+ html: isExistingRow
+ ? 'Select a conception record. The conception Id, dam, sire and species on this birth record will be replaced with the values from that conception. Everything else on the row is left as it is.'
+ : 'Select a conception record. A new birth record will be added using the conception Id, along with the dam, sire and species from that conception.',
style: 'padding-bottom: 10px;'
},{
xtype: 'labkey-combo',
itemId: 'conceptionField',
fieldLabel: 'Conception Id',
+ value: isExistingRow ? this.targetRecord.get('conceptId') : null,
displayField: 'ConceptId',
valueField: 'ConceptId',
forceSelection: true,
@@ -81,14 +90,14 @@ Ext4.define('NBRI_EHR.window.StartWithConceptionWindow', {
btn.disable();
this.getSpecies(dam, function(species, speciesError){
- this.addRow(conceptId, dam, sire, species);
+ this.applyConception(conceptId, dam, sire, species);
btn.enable();
this.close();
- // the row is still added so the conception values are not lost, but a blank species would otherwise
+ // the row is still populated so the conception values are not lost, but a blank species would otherwise
// surface only as a bare "Species is required" error with no hint that the copy from the dam failed
if (speciesError){
- Ext4.Msg.alert('Species Not Copied', speciesError + ' Enter the species on the new birth record manually.');
+ Ext4.Msg.alert('Species Not Copied', speciesError + ' Enter the species on the birth record manually.');
}
}, this);
},
@@ -128,13 +137,21 @@ Ext4.define('NBRI_EHR.window.StartWithConceptionWindow', {
});
},
- addRow: function(conceptId, dam, sire, species){
- this.targetStore.add(this.targetStore.createModel({
+ applyConception: function(conceptId, dam, sire, species){
+ var values = {
conceptId: conceptId,
'Id/demographics/dam': dam,
'Id/demographics/sire': sire,
'Id/demographics/species': species
- }));
+ };
+
+ // only the fields the conception owns are written, so anything already entered on the row survives
+ if (this.targetRecord){
+ this.targetRecord.set(values);
+ return;
+ }
+
+ this.targetStore.add(this.targetStore.createModel(values));
}
});
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java
index 6e988db..0b87214 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java
@@ -218,9 +218,6 @@ private void registerDataEntry()
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIDeathFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIHousingFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIMedicationTreatmentFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIProjectFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIProtocolFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIInvestigatorsFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIPregnancyFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIWeightFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIFlagsFormType.class, this));
@@ -228,10 +225,6 @@ private void registerDataEntry()
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRINotesFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRICasesFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBehavioralCasesFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBuildingFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIRoomFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIFloorFormType.class, this));
- EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRICageFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIClinicalObservationsFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIClinicalRoundsFormType.class, this));
EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIAnimalTrainingFormType.class, this));
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java
index b8d2e6e..b79b90b 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java
@@ -23,9 +23,6 @@
import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRIArrivalFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRIArrivalInstructionsFormSection;
-import org.labkey.nbri_ehr.dataentry.section.NBRIGroupAssignmentFormSection;
-import org.labkey.nbri_ehr.dataentry.section.NBRIProjectAssignmentFormSection;
-import org.labkey.nbri_ehr.dataentry.section.NBRIProtocolAssignmentFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
@@ -43,20 +40,13 @@ public NBRIArrivalFormType(DataEntryFormContext ctx, Module owner)
new NBRITaskFormSection(),
new NBRIAnimalDetailsFormSection(),
new NBRIArrivalFormSection(),
- new NBRIProtocolAssignmentFormSection(true, true, true),
- new NBRIProjectAssignmentFormSection(true, true, true),
- new NBRIGroupAssignmentFormSection(true, true, true),
new NBRIWeightFormSection(true, true)
));
- addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Assignment.js"));
- addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/AnimalGroupMembers.js"));
addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Arrival.js"));
for (FormSection s : getFormSections())
{
- s.addConfigSource("Assignment");
- s.addConfigSource("AnimalGroupMembers");
s.addConfigSource("Arrival");
}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java
index 78be0d6..ebe9c2b 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java
@@ -24,9 +24,6 @@
import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRIBirthFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRIBirthInstructionsFormSection;
-import org.labkey.nbri_ehr.dataentry.section.NBRIGroupAssignmentFormSection;
-import org.labkey.nbri_ehr.dataentry.section.NBRIProjectAssignmentFormSection;
-import org.labkey.nbri_ehr.dataentry.section.NBRIProtocolAssignmentFormSection;
import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
import java.util.ArrayList;
@@ -42,23 +39,16 @@ public NBRIBirthFormType (DataEntryFormContext ctx, Module owner)
new NBRIBirthInstructionsFormSection(),
new NBRITaskFormSection(),
new NBRIAnimalDetailsFormSection(),
- new NBRIBirthFormSection(),
- new NBRIProtocolAssignmentFormSection(true, true, true),
- new NBRIProjectAssignmentFormSection(true, true, true),
- new NBRIGroupAssignmentFormSection(true, true, true)
+ new NBRIBirthFormSection()
));
addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/plugin/RowEditor.js"));
addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/NBRIDefault.js"));
- addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Assignment.js"));
- addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/AnimalGroupMembers.js"));
addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Birth.js"));
addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/AddAnimalsWindow.js"));
for (FormSection s : getFormSections())
{
- s.addConfigSource("Assignment");
- s.addConfigSource("AnimalGroupMembers");
s.addConfigSource("Birth");
}
}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBuildingFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBuildingFormType.java
deleted file mode 100644
index 2628766..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBuildingFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.module.Module;
-import org.labkey.api.security.permissions.AdminPermission;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRIBuildingFormType extends AdminLinksFormType
-{
- public NBRIBuildingFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Building", "Building", "Locations", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr_lookups");
- url.addParameter("query.queryName", "buildings");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICageFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICageFormType.java
deleted file mode 100644
index eb7ed4e..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICageFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.module.Module;
-import org.labkey.api.security.permissions.AdminPermission;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRICageFormType extends AdminLinksFormType
-{
- public NBRICageFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Cage", "Cage", "Locations", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr_lookups");
- url.addParameter("query.queryName", "cage");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFloorFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFloorFormType.java
deleted file mode 100644
index d847464..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFloorFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.module.Module;
-import org.labkey.api.security.permissions.AdminPermission;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRIFloorFormType extends AdminLinksFormType
-{
- public NBRIFloorFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Floor", "Floor", "Locations", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr_lookups");
- url.addParameter("query.queryName", "floors");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIInvestigatorsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIInvestigatorsFormType.java
deleted file mode 100644
index 14055b2..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIInvestigatorsFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.ehr.security.EHRDataAdminPermission;
-import org.labkey.api.module.Module;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRIInvestigatorsFormType extends AdminLinksFormType
-{
- public NBRIInvestigatorsFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Investigators", "Investigators", "Admin", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr");
- url.addParameter("query.queryName", "investigators");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return (super.isAvailable() || getCtx().getContainer().hasPermission(getCtx().getUser(), EHRDataAdminPermission.class));
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProjectFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProjectFormType.java
deleted file mode 100644
index 2a0b6cb..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProjectFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.ehr.security.EHRProtocolEditPermission;
-import org.labkey.api.module.Module;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRIProjectFormType extends AdminLinksFormType
-{
- public NBRIProjectFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Projects", "Projects", "Admin", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr");
- url.addParameter("query.queryName", "project");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return (super.isAvailable() || getCtx().getContainer().hasPermission(getCtx().getUser(), EHRProtocolEditPermission.class));
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProtocolFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProtocolFormType.java
deleted file mode 100644
index 540602a..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProtocolFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.ehr.security.EHRProtocolEditPermission;
-import org.labkey.api.module.Module;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRIProtocolFormType extends AdminLinksFormType
-{
- public NBRIProtocolFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Protocols", "Protocols", "Admin", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr");
- url.addParameter("query.queryName", "protocol");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return (super.isAvailable() || getCtx().getContainer().hasPermission(getCtx().getUser(), EHRProtocolEditPermission.class));
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRoomFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRoomFormType.java
deleted file mode 100644
index 4ce0aa4..0000000
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRoomFormType.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.nbri_ehr.dataentry.form;
-
-import org.labkey.api.ehr.dataentry.DataEntryFormContext;
-import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
-import org.labkey.api.module.Module;
-import org.labkey.api.security.permissions.AdminPermission;
-import org.labkey.api.view.ActionURL;
-
-import java.util.ArrayList;
-
-public class NBRIRoomFormType extends AdminLinksFormType
-{
- public NBRIRoomFormType(DataEntryFormContext ctx, Module owner)
- {
- super(ctx, owner, "Room", "Room", "Locations", new ArrayList<>());
- }
-
- @Override
- protected ActionURL dataEntryLink()
- {
- ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
- url.addParameter("schemaName", "ehr_lookups");
- url.addParameter("query.queryName", "rooms");
- url.addParameter("showImport", "true");
- return url;
- }
-
- @Override
- public boolean isAvailable()
- {
- return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
- }
-}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java
index 326b540..8088c16 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java
@@ -38,6 +38,9 @@ public class NBRIBirthFormSection extends NewAnimalFormSection
FieldKey.fromString("Id/demographics/sire"),
FieldKey.fromString("cage"),
FieldKey.fromString("Id/demographics/socialCode"),
+ FieldKey.fromString("project"),
+ FieldKey.fromString("birthProtocol"),
+ FieldKey.fromString("groupId"),
FieldKey.fromString("type"),
FieldKey.fromString("breedingType"),
FieldKey.fromString("remark"),
@@ -47,9 +50,8 @@ public class NBRIBirthFormSection extends NewAnimalFormSection
public NBRIBirthFormSection()
{
super("study", "birth", "Births", false);
- addClientDependency(ClientDependency.supplierFromPath("ehr/window/FormBulkAddWindow.js"));
- addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/FormBulkAddWindow.js"));
addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/StartWithConceptionWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/ConceptionField.js"));
}
@Override
@@ -84,6 +86,7 @@ public List getTbarButtons()
defaultButtons.remove(idx);
defaultButtons.add(idx, "NBRI_ADDANIMALS");
}
+ defaultButtons.remove("ANIMAL_ID_SERIES");
defaultButtons.remove("COPYFROMSECTION");
defaultButtons.addFirst("NBRI_START_WITH_CONCEPTION");
return defaultButtons;
@@ -94,7 +97,6 @@ public List getTbarMoreActionButtons()
{
List defaultButtons = super.getTbarMoreActionButtons();
defaultButtons.remove("GUESSPROJECT");
- defaultButtons.add("NBRI_FORM_BULK_ADD");
return defaultButtons;
}
}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIConceptionFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIConceptionFormSection.java
index f2cac6a..e8ed5d8 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIConceptionFormSection.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIConceptionFormSection.java
@@ -20,5 +20,8 @@ public class NBRIConceptionFormSection extends BaseFormSection
public NBRIConceptionFormSection(String label)
{
super("nbri_ehr", "conception", label, "ehr-gridpanel", true, false, false);
+
+ // a conception is not recorded against a batch of animals, so drop the Add Batch button
+ setAllowBulkAdd(false);
}
}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java b/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java
index ded4d22..d88b672 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/query/NBRI_EHRTriggerHelper.java
@@ -708,28 +708,28 @@ public String createAssignmentRecord(String dataset, String id, Map> rows = new ArrayList<>();
rows.add(saveRow);
diff --git a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java
index dd7e2c5..9fbbae2 100644
--- a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java
+++ b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java
@@ -118,9 +118,11 @@ public class NBRI_EHRTest extends AbstractGenericEHRTest implements PostgresOnly
private static final String BUILDING_AREA = "SPF";
// Cage locations seeded by populateLocations, named for the room each one sits in. The cage trigger derives these
- // from the room and cage, and housing records key off the location, so these are what belongs in a housing row's
- // 'cage' field. datasetHousing.tsv spells the same values out, since a TSV cannot call cageLocation.
+ // from the room and cage, and both housing records and the data entry cage pickers key off the location, so these
+ // are what belongs in a 'cage' field and what the pickers list. datasetHousing.tsv spells the same values out,
+ // since a TSV cannot call cageLocation.
private static final String CAGE_IN_R1 = cageLocation("R1", "C1");
+ private static final String CAGE_IN_R2 = cageLocation("R2", "C3");
private static final String CAGE_IN_R3 = cageLocation("R3", "C4");
// A group pen has no cage, so its location is the room key alone. Created by testGroupPenCagemates.
@@ -638,7 +640,7 @@ public void testArrivalForm() throws IOException, CommandException
String arrivedAnimal = "30905";
// demographics.socialCode holds an ehr_lookups.social_code code; the grids display its title
String socialCode = "Acquired";
- // animal_group_members.groupId holds an ehr_lookups.breeding_type code; the grids display its title
+ // the group is an ehr_lookups.breeding_type code, carried to animal_group_members; the grids display its title
String animalGroup = "Assigned Breeding Protocol";
LocalDateTime now = LocalDateTime.now();
@@ -652,7 +654,7 @@ public void testArrivalForm() throws IOException, CommandException
arrivals.setGridCell(1, "arrivalType", "Non-quarantine Arrival");
arrivals.setGridCell(1, "acquisitionType", "Lab Transfer (Wild Born)");
arrivals.setGridCell(1, "Id", arrivedAnimal);
- arrivals.setGridCell(1, "cage", "C1");
+ arrivals.setGridCell(1, "cage", CAGE_IN_R1);
arrivals.setGridCell(1, "Id/demographics/gender", "Female");
arrivals.setGridCell(1, "Id/demographics/geographic_origin", "BRAZIL");
arrivals.setGridCell(1, "Id/demographics/species", "Pig-Tailed Macaque");
@@ -665,23 +667,24 @@ public void testArrivalForm() throws IOException, CommandException
waitForFormError("The field: Social Code is required");
arrivals.setGridCell(1, "Id/demographics/socialCode", socialCode);
- Ext4GridRef protocolAssignments = _helper.getExt4GridForFormSection("Protocol Assignment");
- _helper.addRecordToGrid(protocolAssignments);
- protocolAssignments.setGridCell(1, "Id", arrivedAnimal);
- protocolAssignments.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
- protocolAssignments.setGridCell(1, "protocol", "dummyprotocol");
+ // the animal's opening project, protocol and group are entered on the arrival row itself; the trigger script
+ // opens the matching assignment record for each one
+ arrivals.setGridCell(1, "project", "640991");
+ arrivals.setGridCell(1, "arrivalProtocol", "dummyprotocol");
+ arrivals.setGridCell(1, "groupId", animalGroup);
- Ext4GridRef projectAssignments = _helper.getExt4GridForFormSection("Project Assignment");
- _helper.addRecordToGrid(projectAssignments);
- projectAssignments.setGridCell(1, "Id", arrivedAnimal);
- projectAssignments.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
- projectAssignments.setGridCell(1, "project", "640991");
+ log("Verifying the opening project, protocol and group are required");
+ arrivals.setGridCellJS(1, "project", null);
+ waitForFormError("The field: Project is required");
+ arrivals.setGridCell(1, "project", "640991");
- Ext4GridRef groupAssignments = _helper.getExt4GridForFormSection("Group Assignments");
- _helper.addRecordToGrid(groupAssignments);
- groupAssignments.setGridCell(1, "Id", arrivedAnimal);
- groupAssignments.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
- groupAssignments.setGridCell(1, "groupId", animalGroup);
+ arrivals.setGridCellJS(1, "arrivalProtocol", null);
+ waitForFormError("The field: Protocol is required");
+ arrivals.setGridCell(1, "arrivalProtocol", "dummyprotocol");
+
+ arrivals.setGridCellJS(1, "groupId", null);
+ waitForFormError("The field: Group is required");
+ arrivals.setGridCell(1, "groupId", animalGroup);
submitForm("Submit Final", "Finalize");
@@ -692,7 +695,7 @@ public void testArrivalForm() throws IOException, CommandException
view.addColumn("cage");
view.applyCustomView();
Assert.assertEquals("Invalid Arrival record", Arrays.asList(arrivedAnimal), table.getRowDataAsText(0, "Id"));
- Assert.assertEquals("Invalid Arrival record", Arrays.asList("C1"), table.getRowDataAsText(0, "cage"));
+ Assert.assertEquals("Invalid Arrival record", Arrays.asList(CAGE_IN_R1), table.getRowDataAsText(0, "cage"));
goToSchemaBrowser();
table = viewQueryData("study", "assignment");
@@ -744,7 +747,7 @@ public void testBirthForm() throws Exception
String breedingType = "Time-Mated";
// demographics.socialCode holds an ehr_lookups.social_code code; the grids display its title
String socialCode = "Mother-rearing (for indoors)";
- // animal_group_members.groupId holds an ehr_lookups.breeding_type code; the grids display its title
+ // the group is an ehr_lookups.breeding_type code, carried to animal_group_members; the grids display its title
String animalGroup = "Project Breeding";
LocalDateTime now = LocalDateTime.now();
@@ -764,14 +767,7 @@ public void testBirthForm() throws Exception
verifyBirthColumnOrder(births);
log("Starting a birth record from the conception");
- births.clickTbarButton("Start with Conception");
- Window> conceptionWindow = new Window.WindowFinder(getDriver()).withTitle("Start with Conception").waitFor();
- Ext4ComboRef conceptionCombo = _ext4Helper.queryOne("window #conceptionField", Ext4ComboRef.class);
- Assert.assertNotNull("Conception Id field not found in the Start with Conception window", conceptionCombo);
- conceptionCombo.waitForStoreLoad();
- conceptionCombo.setComboByDisplayValue(conceptId);
- conceptionWindow.clickButton("Submit", 0);
- births.waitForRowCount(1);
+ startWithConception(births, conceptId, 1);
log("Verifying the conception populated the new birth record");
assertEquals("Conception Id was not copied from the conception", conceptId, births.getFieldValue(1, "conceptId"));
@@ -786,7 +782,7 @@ public void testBirthForm() throws Exception
births.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
births.setGridCell(1, "Id", bornAnimal);
- births.setGridCell(1, "cage", "C3");
+ births.setGridCell(1, "cage", CAGE_IN_R2);
births.setGridCell(1, "Id/demographics/gender", "Female");
births.setGridCell(1, "breedingType", breedingType);
births.setGridCell(1, "Id/demographics/socialCode", socialCode);
@@ -796,23 +792,24 @@ public void testBirthForm() throws Exception
waitForFormError("The field: Social Code is required");
births.setGridCell(1, "Id/demographics/socialCode", socialCode);
- Ext4GridRef protocolAssignments = _helper.getExt4GridForFormSection("Protocol Assignment");
- _helper.addRecordToGrid(protocolAssignments);
- protocolAssignments.setGridCell(1, "Id", bornAnimal);
- protocolAssignments.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
- protocolAssignments.setGridCell(1, "protocol", "protocol101");
+ // the animal's opening project, protocol and group are entered on the birth row itself; the trigger script
+ // opens the matching assignment record for each one
+ births.setGridCell(1, "project", "795644");
+ births.setGridCell(1, "birthProtocol", "protocol101");
+ births.setGridCell(1, "groupId", animalGroup);
+
+ log("Verifying the opening project, protocol and group are required");
+ births.setGridCellJS(1, "project", null);
+ waitForFormError("The field: Project is required");
+ births.setGridCell(1, "project", "795644");
- Ext4GridRef projectAssignments = _helper.getExt4GridForFormSection("Project Assignment");
- _helper.addRecordToGrid(projectAssignments);
- projectAssignments.setGridCell(1, "Id", bornAnimal);
- projectAssignments.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
- projectAssignments.setGridCell(1, "project", "795644");
+ births.setGridCellJS(1, "birthProtocol", null);
+ waitForFormError("The field: Protocol is required");
+ births.setGridCell(1, "birthProtocol", "protocol101");
- Ext4GridRef groupAssignments = _helper.getExt4GridForFormSection("Group Assignments");
- _helper.addRecordToGrid(groupAssignments);
- groupAssignments.setGridCell(1, "Id", bornAnimal);
- groupAssignments.setGridCellJS(1, "date", now.minusDays(1).format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
- groupAssignments.setGridCell(1, "groupId", animalGroup);
+ births.setGridCellJS(1, "groupId", null);
+ waitForFormError("The field: Group is required");
+ births.setGridCell(1, "groupId", animalGroup);
submitForm("Submit Final", "Finalize");
@@ -820,7 +817,7 @@ public void testBirthForm() throws Exception
DataRegionTable table = viewQueryData("study", "birth");
table.setFilter("Id", "Equals", bornAnimal);
Assert.assertEquals("Invalid Birth record", Arrays.asList(bornAnimal), table.getRowDataAsText(0, "Id"));
- Assert.assertEquals("Invalid Birth record", Arrays.asList("C3"), table.getRowDataAsText(0, "cage"));
+ Assert.assertEquals("Invalid Birth record", Arrays.asList(CAGE_IN_R2), table.getRowDataAsText(0, "cage"));
Assert.assertEquals("Invalid Birth record", Arrays.asList(conceptId), table.getRowDataAsText(0, "conceptId"));
Assert.assertEquals("Invalid Birth record", Arrays.asList(breedingType), table.getRowDataAsText(0, "breedingType"));
@@ -871,6 +868,166 @@ public void testBirthForm() throws Exception
Assert.assertEquals("Invalid ConceptionsByDam row", Arrays.asList(bornAnimal), report.getRowDataAsText(0, "offspring"));
}
+ @Test
+ public void testDuplicateConceptionRejected() throws Exception
+ {
+ String firstAnimal = "80811";
+ String secondAnimal = "80812";
+ String damId = "TESTDAM03";
+ String sireId = "TESTSIRE03";
+ String damSpeciesCode = "CAP";
+ String firstConcept = "TESTCONCEPT4";
+ String secondConcept = "TESTCONCEPT5";
+ String socialCode = "Mother-rearing (for indoors)";
+ String animalGroup = "Project Breeding";
+ // the severity prefix is part of the message the server sends, so asserting on it also pins the rule at
+ // ERROR, which is what refuses the save
+ String duplicateError = "ERROR: This conception Id is already used by another birth record";
+ LocalDateTime now = LocalDateTime.now();
+
+ log("Creating the dam and sire of the conceptions");
+ createBreedingPair(damId, sireId, damSpeciesCode);
+
+ log("Creating two conception records for that pair");
+ InsertRowsCommand conceptions = new InsertRowsCommand("nbri_ehr", "Conception");
+ conceptions.addRow(Map.of("ConceptId", firstConcept, "ConceptDate", now.minusDays(200), "Dam", damId, "Sire", sireId));
+ conceptions.addRow(Map.of("ConceptId", secondConcept, "ConceptDate", now.minusDays(160), "Dam", damId, "Sire", sireId));
+ conceptions.execute(getApiHelper().getConnection(), getContainerPath());
+
+ gotoEnterData();
+ waitAndClickAndWait(Locator.linkWithText("Birth"));
+ lockForm();
+
+ Ext4GridRef births = _helper.getExt4GridForFormSection("Births");
+
+ log("Entering two births that both claim the first conception");
+ startWithConception(births, firstConcept, 1);
+ fillBirthRow(births, 1, firstAnimal, now.minusDays(1), socialCode, animalGroup);
+ startWithConception(births, firstConcept, 2);
+ fillBirthRow(births, 2, secondAnimal, now.minusDays(1), socialCode, animalGroup);
+
+ // Live validation only sends the row that just changed, so the rows of one form entry first reach the
+ // server together on submit. A rule that compares them therefore reports by refusing the save rather than
+ // while the form is being filled.
+ log("Verifying the save is refused while both births claim one conception");
+ submitFormExpectingError(duplicateError);
+
+ log("Verifying the save is accepted once the second birth points at its own conception");
+ births.setGridCellJS(2, "conceptId", secondConcept);
+ waitForNoFormError(duplicateError);
+
+ submitForm("Submit Final", "Finalize");
+
+ goToSchemaBrowser();
+ DataRegionTable table = viewQueryData("study", "birth");
+ table.setFilter("conceptId", "Equals", firstConcept);
+ Assert.assertEquals("The first conception should have exactly one birth", 1, table.getDataRowCount());
+ Assert.assertEquals("Wrong birth recorded against the first conception",
+ Arrays.asList(firstAnimal), table.getRowDataAsText(0, "Id"));
+
+ goToSchemaBrowser();
+ table = viewQueryData("study", "birth");
+ table.setFilter("conceptId", "Equals", secondConcept);
+ Assert.assertEquals("The second conception should have exactly one birth", 1, table.getDataRowCount());
+ Assert.assertEquals("Wrong birth recorded against the second conception",
+ Arrays.asList(secondAnimal), table.getRowDataAsText(0, "Id"));
+
+ // the refused save is logged server side, so account for it or the run's server error check fails the test
+ checkExpectedErrors(1);
+ }
+
+ @Test
+ public void testConceptionPickedFromGridCell() throws Exception
+ {
+ String bornAnimal = "80813";
+ String firstDam = "TESTDAM04";
+ String firstSire = "TESTSIRE04";
+ String secondDam = "TESTDAM05";
+ String secondSire = "TESTSIRE05";
+ // different species on each pair, so the copy from the newly picked dam is visible
+ String firstSpeciesCode = "CAP";
+ String secondSpeciesCode = "MMU";
+ String firstConcept = "TESTCONCEPT6";
+ String secondConcept = "TESTCONCEPT7";
+ String socialCode = "Mother-rearing (for indoors)";
+ String animalGroup = "Project Breeding";
+ LocalDateTime now = LocalDateTime.now();
+
+ log("Creating a breeding pair and a conception for each");
+ createBreedingPair(firstDam, firstSire, firstSpeciesCode);
+ createBreedingPair(secondDam, secondSire, secondSpeciesCode);
+
+ InsertRowsCommand conceptions = new InsertRowsCommand("nbri_ehr", "Conception");
+ conceptions.addRow(Map.of("ConceptId", firstConcept, "ConceptDate", now.minusDays(200), "Dam", firstDam, "Sire", firstSire));
+ conceptions.addRow(Map.of("ConceptId", secondConcept, "ConceptDate", now.minusDays(190), "Dam", secondDam, "Sire", secondSire));
+ conceptions.execute(getApiHelper().getConnection(), getContainerPath());
+
+ gotoEnterData();
+ waitAndClickAndWait(Locator.linkWithText("Birth"));
+ lockForm();
+
+ Ext4GridRef births = _helper.getExt4GridForFormSection("Births");
+ startWithConception(births, firstConcept, 1);
+ fillBirthRow(births, 1, bornAnimal, now.minusDays(1), socialCode, animalGroup);
+ births.setGridCell(1, "breedingType", "Time-Mated");
+
+ // the codes behind these lookups are not spelled out in the test, so remember what the row carries and
+ // compare afterwards rather than asserting against a literal
+ Object dateBefore = births.getFieldValue(1, "date");
+ Object genderBefore = births.getFieldValue(1, "Id/demographics/gender");
+ Object socialCodeBefore = births.getFieldValue(1, "Id/demographics/socialCode");
+ Object breedingTypeBefore = births.getFieldValue(1, "breedingType");
+ Object groupBefore = births.getFieldValue(1, "groupId");
+
+ log("Opening the picker from the Conception Id cell of the existing row");
+ // a single click starts the grid's editor, which focuses the field and opens the window; startEditing() is
+ // avoided here because it retries the click when it cannot find a focused editor behind the modal window
+ births.getCell(1, "conceptId").findElement(getDriver()).click();
+ Window> changeWindow = new Window.WindowFinder(getDriver()).withTitle("Change Conception").waitFor();
+ Ext4ComboRef conceptionCombo = _ext4Helper.queryOne("window #conceptionField", Ext4ComboRef.class);
+ Assert.assertNotNull("Conception Id field not found in the Change Conception window", conceptionCombo);
+ conceptionCombo.waitForStoreLoad();
+ Assert.assertEquals("The window should open on the conception the row already carries",
+ firstConcept, conceptionCombo.getValue());
+
+ conceptionCombo.setComboByDisplayValue(secondConcept);
+ changeWindow.clickButton("Submit", 0);
+
+ log("Verifying the picked conception replaced the conception fields on that row");
+ waitFor(() -> secondConcept.equals(births.getFieldValue(1, "conceptId")),
+ "Conception Id was not replaced by the picked conception", WAIT_FOR_JAVASCRIPT);
+ assertEquals("Dam was not replaced from the picked conception", secondDam, births.getFieldValue(1, "Id/demographics/dam"));
+ assertEquals("Sire was not replaced from the picked conception", secondSire, births.getFieldValue(1, "Id/demographics/sire"));
+ assertEquals("Species was not replaced from the dam of the picked conception", secondSpeciesCode, births.getFieldValue(1, "Id/demographics/species"));
+
+ log("Verifying nothing else on the row was touched");
+ assertEquals("Animal Id should have been left alone", bornAnimal, births.getFieldValue(1, "Id"));
+ assertEquals("Birth date should have been left alone", dateBefore, births.getFieldValue(1, "date"));
+ assertEquals("Gender should have been left alone", genderBefore, births.getFieldValue(1, "Id/demographics/gender"));
+ assertEquals("Social Code should have been left alone", socialCodeBefore, births.getFieldValue(1, "Id/demographics/socialCode"));
+ assertEquals("Breeding Type should have been left alone", breedingTypeBefore, births.getFieldValue(1, "breedingType"));
+ assertEquals("Group should have been left alone", groupBefore, births.getFieldValue(1, "groupId"));
+ assertEquals("Project should have been left alone", "795644", String.valueOf(births.getFieldValue(1, "project")));
+ assertEquals("Protocol should have been left alone", "protocol101", births.getFieldValue(1, "birthProtocol"));
+
+ submitForm("Submit Final", "Finalize");
+
+ log("Verifying the saved birth carries the picked conception");
+ goToSchemaBrowser();
+ DataRegionTable table = viewQueryData("study", "birth");
+ table.setFilter("Id", "Equals", bornAnimal);
+ Assert.assertEquals("Saved birth does not carry the picked conception",
+ Arrays.asList(secondConcept), table.getRowDataAsText(0, "conceptId"));
+
+ goToSchemaBrowser();
+ table = viewQueryData("study", "demographics");
+ table.setFilter("Id", "Equals", bornAnimal);
+ Assert.assertEquals("Demographics does not carry the dam of the picked conception",
+ Arrays.asList(secondDam), table.getRowDataAsText(0, "dam"));
+ Assert.assertEquals("Demographics does not carry the sire of the picked conception",
+ Arrays.asList(secondSire), table.getRowDataAsText(0, "sire"));
+ }
+
@Test
public void testPregnancyForm() throws IOException, CommandException
{
@@ -2064,6 +2221,34 @@ private int countLines(File file) throws Exception
// Creates the parents of a conception. They need an ehr_lookups.species_codes code because the Start with
// Conception window copies the dam's species onto the newborn, and the test asserts the resulting record.
+ // Adds a birth row through the Start with Conception window, the only way to fill the conception fields on a row
+ // that does not exist yet.
+ private void startWithConception(Ext4GridRef births, String conceptId, int expectedRowCount)
+ {
+ births.clickTbarButton("Start with Conception");
+ Window> conceptionWindow = new Window.WindowFinder(getDriver()).withTitle("Start with Conception").waitFor();
+ Ext4ComboRef conceptionCombo = _ext4Helper.queryOne("window #conceptionField", Ext4ComboRef.class);
+ Assert.assertNotNull("Conception Id field not found in the Start with Conception window", conceptionCombo);
+ conceptionCombo.waitForStoreLoad();
+ conceptionCombo.setComboByDisplayValue(conceptId);
+ conceptionWindow.clickButton("Submit", 0);
+ births.waitForRowCount(expectedRowCount);
+ }
+
+ // Fills in everything a birth row needs beyond what the conception supplies, so the form can be submitted.
+ // Birth Location is left blank on purpose: it is optional, and skipping it keeps housing out of these tests.
+ private void fillBirthRow(Ext4GridRef births, int rowIdx, String animalId, LocalDateTime birthDate,
+ String socialCode, String animalGroup)
+ {
+ births.setGridCellJS(rowIdx, "date", birthDate.format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING)));
+ births.setGridCell(rowIdx, "Id", animalId);
+ births.setGridCell(rowIdx, "Id/demographics/gender", "Female");
+ births.setGridCell(rowIdx, "Id/demographics/socialCode", socialCode);
+ births.setGridCell(rowIdx, "project", "795644");
+ births.setGridCell(rowIdx, "birthProtocol", "protocol101");
+ births.setGridCell(rowIdx, "groupId", animalGroup);
+ }
+
private void createBreedingPair(String damId, String sireId, String species) throws Exception
{
String[] fields = new String[]{"Id", "Species", "Birth", "Gender", "date", "calculated_status", "objectid", "performedby"};
@@ -2081,8 +2266,8 @@ private void createBreedingPair(String damId, String sireId, String species) thr
private void verifyBirthColumnOrder(Ext4GridRef births)
{
List expectedOrder = List.of("Id", "date", "conceptId", "Id/demographics/species", "Id/demographics/gender",
- "Id/demographics/dam", "Id/demographics/sire", "cage", "Id/demographics/socialCode", "type",
- "breedingType", "remark", "performedby");
+ "Id/demographics/dam", "Id/demographics/sire", "cage", "Id/demographics/socialCode", "project",
+ "birthProtocol", "groupId", "type", "breedingType", "remark", "performedby");
int previousIdx = 0;
String previousCol = null;
@@ -2100,6 +2285,11 @@ private void waitForFormError(String message)
waitFor(() -> isTextPresent(message), "Form did not report: " + message, WAIT_FOR_JAVASCRIPT);
}
+ private void waitForNoFormError(String message)
+ {
+ waitFor(() -> !isTextPresent(message), "Form kept reporting: " + message, WAIT_FOR_JAVASCRIPT);
+ }
+
/**
* Reads a date field for one animal through the API rather than off a grid, so assertions compare stored values
* instead of formatted display text, and normalizes to the day: event dates are entered with the time stripped,
@@ -2181,6 +2371,11 @@ private void setFormSectionFieldUntilWarningClears(String section, String label,
}
private void submitForm(String buttonText, String windowTitle)
+ {
+ submitForm(buttonText, windowTitle, true);
+ }
+
+ private void submitForm(String buttonText, String windowTitle, boolean expectNavigation)
{
//Give time for errors to disappear after validation
Locator.tagContainingText("div", "The form has the following errors and warnings:")
@@ -2200,7 +2395,24 @@ private void submitForm(String buttonText, String windowTitle)
submitFinalBtn.findElement(getDriver()).click();
msgWindow = new Window.WindowFinder(this.getDriver()).withTitleContaining(windowTitle).waitFor();
}
- msgWindow.clickButton("Yes");
+ // a successful save navigates to the form's success URL, but a refused one leaves the browser on the form,
+ // so only the former can wait for a page load
+ if (expectNavigation)
+ msgWindow.clickButton("Yes");
+ else
+ msgWindow.clickButton("Yes", 0);
+ }
+
+ /**
+ * Submits the form expecting the save to be refused, and asserts the given message is reported. Dismisses the
+ * alert the panel raises so the form can be corrected and submitted again. A refused save is logged as a server
+ * error, which the caller has to account for through checkExpectedErrors.
+ */
+ private void submitFormExpectingError(String message)
+ {
+ submitForm("Submit Final", "Finalize", false);
+ waitForFormError(message);
+ new Window.WindowFinder(getDriver()).withTitle("Error").waitFor().clickButton("OK", 0);
}
private void gotoEnterData()