diff --git a/GeneticsCore/resources/etls/MHC_Typing.xml b/GeneticsCore/resources/etls/MHC_Typing.xml
index 1434f9e22..1b8134db3 100644
--- a/GeneticsCore/resources/etls/MHC_Typing.xml
+++ b/GeneticsCore/resources/etls/MHC_Typing.xml
@@ -22,6 +22,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/onprc_billing/resources/queries/onprc_billing/perDiemsByDay.sql b/onprc_billing/resources/queries/onprc_billing/perDiemsByDay.sql
index 8c890ad80..cee121ac8 100644
--- a/onprc_billing/resources/queries/onprc_billing/perDiemsByDay.sql
+++ b/onprc_billing/resources/queries/onprc_billing/perDiemsByDay.sql
@@ -2,193 +2,188 @@
SELECT
t.*,
CASE
- WHEN t.overlappingProjects IS NULL then 1
- WHEN t.tmbAssignments > 0 then 0 --note: when co-assigned to TMB, per diem is never charged. if TMB is single-assigned, it needs to pay per diem, and will be caught above
+ WHEN t.overlappingProjects IS NULL THEN 1
+ -- NOTE: An assignment overlapping with TMB is not charged per diems. If TMB is single-assigned, it pays per diem and will be caught above.
+ WHEN t.tmbAssignments > 0 then 0
WHEN t.assignedProject IS NULL AND t.overlappingProjects IS NOT NULL THEN 0
- WHEN t.ProjectType != 'Research' and t.overlappingProjectsCategory LIKE '%Research%' Then 0
- WHEN t.ProjectType != 'Research' and t.overlappingProjectsCategory NOT LIKE '%Research%' Then (1.0 / NULLIF((t.totalOverlappingProjects + 1), 0))
- WHEN t.ProjectType = 'Research' and t.overlappingProjectsCategory NOT LIKE '%Research%' Then 1
- WHEN t.ProjectType = 'Research' and t.overlappingProjectsCategory LIKE '%Research%' Then (1.0 / NULLIF((t.totalOverlappingResearchProjects + 1), 0))
- ELSE 1
-END as effectiveDays,
- CASE
- WHEN (t.assignedProject IS NULL AND t.overlappingProjects IS NULL) THEN 'Base Grant'
- WHEN t.overlappingProjects IS NULL then 'Single Project'
- WHEN (t.tmbAssignments > 0) then 'Exempt By TMB'
- WHEN (t.isTMBProject = 1 AND t.overlappingProjects IS NOT NULL) then 'Exempt By TMB'
- WHEN t.assignedProject IS NULL AND t.overlappingProjects IS NOT NULL THEN 'Paid By Overlapping Project'
- WHEN t.ProjectType != 'Research' and t.overlappingProjectsCategory LIKE '%Research%' Then 'Paid By Overlapping Project'
- WHEN t.ProjectType != 'Research' and t.overlappingProjectsCategory NOT LIKE '%Research%' Then 'Multiple Resources'
- WHEN t.ProjectType = 'Research' and t.overlappingProjectsCategory NOT LIKE '%Research%' Then 'Single Project'
- WHEN t.ProjectType = 'Research' and t.overlappingProjectsCategory LIKE '%Research%' Then 'Multiple Research'
- ELSE 'Unknown'
-END as category,
-
- CASE
- WHEN (t.perDiemFeeCount > 1) THEN null --this should catch duplicate chargeIds
- --use the treatmentOtrder to look for BottleFed
- WHEN (t.bottleFedRecordCount > 0 AND t.researchRecordCount > 0) THEN maxPdfChargeId
- --if this item supports infants, charge that
-
- WHEN (pdfChargeInfantCount > 0 AND maxPdfChargeId IS NOT NULL) THEN maxPdfChargeId
- --otherwise infants are a special rate
- WHEN (perDiemAge < CAST(javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.INFANT_PER_DIEM_AGE') AS INTEGER)) THEN (SELECT ci.rowid FROM onprc_billing_public.chargeableItems ci WHERE ci.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.INFANT_PER_DIEM'))
- --add quarantine flags, which trump housing type
- WHEN (quarantineFlagCount > 0) THEN (SELECT ci.rowid FROM onprc_billing_public.chargeableItems ci WHERE ci.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.QUARANTINE_PER_DIEM'))
- --finally defer to housing condition
- ELSE maxPdfChargeId
-END as chargeId,
-
- --find overlapping tier flags on that day
- coalesce((
- SELECT group_concat(DISTINCT f.flag.value) as tier
- FROM study.flags f
- --NOTE: allow flags that ended on this date
- WHERE f.Id = t.Id AND f.enddateCoalesced >= t.dateOnly AND f.dateOnly <= t.dateOnly AND f.flag.category = 'Housing Tier'
- ), 'Tier 2') as tier
-
-FROM (
-
-SELECT
- i2.Id,
- CAST(CAST(i2.dateOnly as date) as timestamp) as date,
- i2.dateOnly @hidden,
- coalesce(a.project, (SELECT p.project FROM ehr.project p WHERE p.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.BASE_GRANT_PROJECT'))) as project,
- a.project as assignedProject,
- max(a.duration) as duration, --should only have 1 value, no so need to include in grouping
- max(timestampdiff('SQL_TSI_DAY', d.birth, i2.dateOnly)) as ageAtTime,
- a.project.use_Category as ProjectType,
- count(*) as totalAssignmentRecords,
- group_concat(DISTINCT a2.project.displayName) as overlappingProjects,
- count(DISTINCT a2.project) as totalOverlappingProjects,
- sum(CASE WHEN a2.project.use_Category = 'Research' THEN 1 ELSE 0 END) as totalOverlappingResearchProjects,
- group_concat(DISTINCT a2.project.use_category) as overlappingProjectsCategory,
- group_concat(DISTINCT a2.project.protocol) as overlappingProtocols,
- count(h3.room) as totalHousingRecords,
- group_concat(DISTINCT h3.room) as rooms,
- group_concat(DISTINCT h3.cage) as cages,
- group_concat(DISTINCT h3.objectid) as housingRecords,
- group_concat(DISTINCT a.objectid) as assignmentRecords,
- group_concat(DISTINCT h3.room.housingCondition.value) as housingConditions,
- group_concat(DISTINCT h3.room.housingType.value) as housingTypes,
- max(timestampdiff('SQL_TSI_DAY', d.birth, i2.dateOnly)) as perDiemAge,
- count(DISTINCT pdf.chargeId) as perDiemFeeCount,
- count(t1.code) AS bottleFedRecordCount,
- count(a3.project) AS researchRecordCount,
- count(CASE WHEN pdf.canChargeInfants = true THEN 1 ELSE null END) AS pdfChargeInfantCount,
- max(pdf.chargeId) AS maxPdfChargeId,
- (SELECT count(*) AS c FROM study.flags q WHERE q.Id = i2.Id AND q.flag.value LIKE '%Quarantine%' AND q.dateOnly <= i2.dateOnly AND q.enddateCoalesced >= i2.dateOnly) AS quarantineFlagCount,
- max(i2.startDate) as startDate @hidden,
- count(tmb.Id) as tmbAssignments,
- SUM(CASE WHEN a.projectName = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.TMB_PROJECT') THEN 1 ELSE 0 END) as isTMBProject
+ WHEN t.ProjectType != 'Research' AND t.overlappingProjectsCategory LIKE '%Research%' THEN 0
+ WHEN t.ProjectType != 'Research' AND t.overlappingProjectsCategory NOT LIKE '%Research%' THEN (1.0 / NULLIF((t.totalOverlappingProjects + 1), 0))
+ WHEN t.ProjectType = 'Research' AND t.overlappingProjectsCategory NOT LIKE '%Research%' THEN 1
+ WHEN t.ProjectType = 'Research' AND t.overlappingProjectsCategory LIKE '%Research%' THEN (1.0 / NULLIF((t.totalOverlappingResearchProjects + 1), 0))
+ ELSE 1
+ END as effectiveDays,
+ CASE
+ WHEN (t.assignedProject IS NULL AND t.overlappingProjects IS NULL) THEN 'Base Grant'
+ WHEN t.overlappingProjects IS NULL then 'Single Project'
+ WHEN (t.tmbAssignments > 0) THEN 'Exempt By TMB'
+ WHEN (t.isTMBProject = 1 AND t.overlappingProjects IS NOT NULL) THEN 'Exempt By TMB'
+ WHEN t.assignedProject IS NULL AND t.overlappingProjects IS NOT NULL THEN 'Paid By Overlapping Project'
+ WHEN t.ProjectType != 'Research' AND t.overlappingProjectsCategory LIKE '%Research%' THEN 'Paid By Overlapping Project'
+ WHEN t.ProjectType != 'Research' AND t.overlappingProjectsCategory NOT LIKE '%Research%' THEN 'Multiple Resources'
+ WHEN t.ProjectType = 'Research' AND t.overlappingProjectsCategory NOT LIKE '%Research%' THEN 'Single Project'
+ WHEN t.ProjectType = 'Research' AND t.overlappingProjectsCategory LIKE '%Research%' THEN 'Multiple Research'
+ ELSE 'Unknown'
+ END as category,
+ CASE
+ -- Catch duplicate chargeIds
+ WHEN (t.perDiemFeeCount > 1) THEN NULL
+ -- Use the treatmentOrder to look for BottleFed
+ WHEN (t.bottleFedRecordCount > 0 AND t.researchRecordCount > 0) THEN maxPdfChargeId
+ -- If this item supports infants, charge that
+ WHEN (pdfChargeInfantCount > 0 AND maxPdfChargeId IS NOT NULL) THEN maxPdfChargeId
+ -- Otherwise, infants are a special rate
+ WHEN (perDiemAge < CAST(javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.INFANT_PER_DIEM_AGE') AS INTEGER))
+ THEN (SELECT ci.rowid FROM onprc_billing_public.chargeableItems ci WHERE ci.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.INFANT_PER_DIEM'))
+ -- Add quarantine flags, which trump housing type
+ WHEN (quarantineFlagCount > 0)
+ THEN (SELECT ci.rowid FROM onprc_billing_public.chargeableItems ci WHERE ci.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.QUARANTINE_PER_DIEM'))
+ -- Finally, defer to housing condition
+ ELSE maxPdfChargeId
+ END as chargeId,
+ -- Find overlapping tier flags on that day
+ coalesce((
+ SELECT group_concat(DISTINCT f.flag.value) AS tier
+ FROM study.flags f
+ -- NOTE: allow flags that ended on this date
+ WHERE f.Id = t.Id AND f.enddateCoalesced >= t.dateOnly AND f.dateOnly <= t.dateOnly AND f.flag.category = 'Housing Tier'
+ ), 'Tier 2') AS tier
FROM (
- -- find all distinct animals housed here on each day. this was moved to be the
- -- first join so we can be sure to include any animal housed here on that day,
- -- as opposed to only assigned animals
- SELECT
- h.Id,
- i.dateOnly,
- max(h.date) as lastHousingStart,
- min(i.startDate) as startDate @hidden
- FROM ldk.dateRange i
- JOIN study.housing h ON (h.dateOnly <= i.dateOnly AND h.enddateCoalesced >= i.dateOnly AND h.qcstate.publicdata = true)
- --WHERE i.dateOnly <= curdate()
- GROUP BY h.Id, i.dateOnly
-) i2
-
-JOIN study.demographics d ON (
- i2.Id = d.Id
-)
-
-LEFT JOIN study.treatment_Order t1 ON t1.id = i2.id AND t1.code.meaning LIKE '%Bottle%' AND t1.date <=i2.dateOnly
-
-LEFT JOIN study.assignment a3 ON a3.id = i2.id AND a3.date <= i2.dateOnly AND a3.endDateCoalesced > i2.dateOnly AND a3.project.Use_Category LIKE '%Research%'
-
--- housing is a little tricky. using the query above, we want to find the max start date, on or before this day
--- the housingType from this room will be used
-JOIN study.housing h3 ON (h3.Id = i2.Id AND i2.lastHousingStart = h3.date AND h3.qcstate.publicdata = true)
-
---then join to any assignment record overlapping each day
-LEFT JOIN (
- SELECT
- a.lsid,
- a.id,
- a.project,
- a.project.name as projectName,
- a.date,
- a.assignCondition,
- a.releaseCondition,
- a.projectedReleaseCondition,
- a.duration,
- a.enddate,
- a.dateOnly,
- a.enddateCoalesced,
- a.objectid
- FROM study.assignment a
-
- WHERE a.qcstate.publicdata = true
- --NOTE: we might want to exclude 1-day assignments, or deal with them differently
- --AND a.duration > 0
-
-) a ON (
- i2.Id = a.id AND
- a.dateOnly <= i2.dateOnly
- --assignments end at midnight, so an assignment doesnt count on the current date if it ends on it
- --NOTE: we do need to capture 1-day assignments, so these do count if the start and end are the same day
- AND (a.enddate IS NULL OR a.enddateCoalesced > i2.dateOnly OR (a.dateOnly = i2.dateOnly AND a.enddateCoalesced = i2.dateOnly))
- )
-
-LEFT JOIN (
- --for each assignment, find co-assigned projects on that day
- SELECT
- a2.lsid,
- a2.date,
- a2.enddate,
- a2.id,
- a2.project,
- a2.dateOnly,
- a2.enddateCoalesced
- FROM study.assignment a2
- WHERE
- --NOTE: this has been reversed. if one-day assignments are exempted from per diems, this should be restored
- --exclude 1-day assignments
- --a2.duration > 1 AND
- a2.qcstate.publicdata = true
-) a2 ON (
- i2.id = a2.id
- AND a2.dateOnly <= i2.dateOnly
- AND a.project != a2.project
- --assignments end at midnight, so an assignment doesnt count on the current date if it ends on it
- --we also need to include 1-day assignments
- AND (a2.enddate IS NULL OR a2.enddateCoalesced > i2.dateOnly OR (a2.dateOnly = i2.dateOnly AND a2.enddateCoalesced = i2.dateOnly))
- AND a.lsid != a2.lsid
-)
-
---find overlapping TMB on this date, which overrides the per diem
---note: also include them if the current project is TMB, as this is also exempt
-LEFT JOIN study.assignment tmb ON (
- a.id = tmb.id
- AND tmb.dateOnly <= i2.dateOnly
- and tmb.project != a.project
- AND tmb.endDateCoalesced >= i2.dateOnly
- AND tmb.project.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.TMB_PROJECT')
-)
-
-LEFT JOIN onprc_billing.perDiemFeeDefinition pdf
-ON (
- pdf.housingType = h3.room.housingType AND
- pdf.housingDefinition = h3.room.housingCondition AND
-
- --find overlapping tier flags on that day
- coalesce((
- SELECT group_concat(DISTINCT f.flag.value) as tier
- FROM study.flags f
- --NOTE: allow flags that ended on this date
- WHERE f.Id = i2.Id AND f.enddateCoalesced >= i2.dateOnly AND f.dateOnly <= i2.dateOnly AND f.flag.category = 'Housing Tier'
- ), 'Tier 2') = pdf.tier
-)
-
-GROUP BY i2.dateOnly, I2.Id, a.project, a.project.use_Category
+ SELECT
+ i2.Id,
+ CAST(CAST(i2.dateOnly AS DATE) AS TIMESTAMP) AS DATE,
+ i2.dateOnly @hidden,
+ coalesce(a.project, (SELECT p.project FROM ehr.project p WHERE p.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.BASE_GRANT_PROJECT'))) AS project,
+ a.project AS assignedProject,
+ max(a.duration) AS duration, -- should only have 1 value, no so need to include in grouping
+ max(timestampdiff('SQL_TSI_DAY', d.birth, i2.dateOnly)) AS ageAtTime,
+ a.project.use_Category AS ProjectType,
+ count(*) AS totalAssignmentRecords,
+ group_concat(DISTINCT a2.project.displayName) AS overlappingProjects,
+ count(DISTINCT a2.project) AS totalOverlappingProjects,
+ sum(CASE WHEN a2.project.use_Category = 'Research' THEN 1 ELSE 0 END) as totalOverlappingResearchProjects,
+ group_concat(DISTINCT a2.project.use_category) AS overlappingProjectsCategory,
+ group_concat(DISTINCT a2.project.protocol) AS overlappingProtocols,
+ count(h3.room) AS totalHousingRecords,
+ group_concat(DISTINCT h3.room) AS rooms,
+ group_concat(DISTINCT h3.cage) AS cages,
+ group_concat(DISTINCT h3.objectid) AS housingRecords,
+ group_concat(DISTINCT a.objectid) AS assignmentRecords,
+ group_concat(DISTINCT h3.room.housingCondition.value) AS housingConditions,
+ group_concat(DISTINCT h3.room.housingType.value) AS housingTypes,
+ max(timestampdiff('SQL_TSI_DAY', d.birth, i2.dateOnly)) AS perDiemAge,
+ count(DISTINCT pdf.chargeId) AS perDiemFeeCount,
+ i2.researchRecordCount,
+ i2.bottleFedRecordCount,
+ count(CASE WHEN pdf.canChargeInfants = TRUE THEN 1 ELSE NULL END) AS pdfChargeInfantCount,
+ max(pdf.chargeId) AS maxPdfChargeId,
+ (SELECT count(*) AS c
+ FROM study.flags q
+ WHERE q.Id = i2.Id AND q.flag.value LIKE '%Quarantine%' AND q.dateOnly <= i2.dateOnly AND q.enddateCoalesced >= i2.dateOnly
+ ) AS quarantineFlagCount,
+ max(i2.startDate) AS startDate @hidden,
+ count(tmb.Id) AS tmbAssignments,
+ SUM(CASE WHEN a.projectName = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.TMB_PROJECT') THEN 1 ELSE 0 END) AS isTMBProject
+
+ FROM (
+ -- Find all distinct animals housed at the Center each day.
+ -- This is the first dataset to include all animals here, not just assigned animals.
+ SELECT
+ h.Id,
+ i.dateOnly,
+ max(h.date) AS lastHousingStart,
+ min(i.startDate) AS startDate @hidden,
+ count(a3.project) as researchRecordCount,
+ count(t1.code) as bottleFedRecordCount
+ FROM ldk.dateRange i
+ JOIN study.housing h ON (h.dateOnly <= i.dateOnly AND h.enddateCoalesced >= i.dateOnly AND h.qcstate.publicdata = TRUE)
+ LEFT JOIN study.assignment a3 ON a3.id = h.id AND a3.date <= i.dateOnly AND a3.endDateCoalesced > i.dateOnly AND a3.project.Use_Category LIKE '%Research%'
+ LEFT JOIN study.treatment_Order t1 ON t1.id = h.id AND t1.code.meaning LIKE '%Bottle%' AND t1.date <= i.dateOnly
+ GROUP BY h.Id, i.dateOnly
+ ) i2
+
+ JOIN study.demographics d ON (
+ i2.Id = d.Id
+ )
+
+ -- Housing is a little tricky. Using the query above, we want to find the max start date, on or before this day.
+ -- The housingType from this location is used.
+ JOIN study.housing h3 ON (h3.Id = i2.Id AND i2.lastHousingStart = h3.date AND h3.qcstate.publicdata = TRUE)
+
+ -- Then join to any assignment record overlapping each day
+ LEFT JOIN (
+ SELECT
+ a.lsid,
+ a.id,
+ a.project,
+ a.project.name AS projectName,
+ a.date,
+ a.assignCondition,
+ a.releaseCondition,
+ a.projectedReleaseCondition,
+ a.duration,
+ a.enddate,
+ a.dateOnly,
+ a.enddateCoalesced,
+ a.objectid
+ FROM study.assignment a
+ WHERE a.qcstate.publicdata = TRUE
+ -- NOTE: We don't exclude 1-day assignments or treat them differently.
+ -- AND a.duration > 0
+ ) a ON (
+ i2.Id = a.id
+ AND a.dateOnly <= i2.dateOnly
+ -- Assignments end at midnight, so an assignment doesn't count on the current date if it ends on it.
+ -- However, we also include 1-day assignments, which *can* have the end date match the start date.
+ AND (a.enddate IS NULL OR a.enddateCoalesced > i2.dateOnly OR (a.dateOnly = i2.dateOnly AND a.enddateCoalesced = i2.dateOnly))
+ )
+
+ LEFT JOIN (
+ -- For each assignment, find the co-assigned projects on that day.
+ SELECT
+ a2.lsid,
+ a2.date,
+ a2.enddate,
+ a2.id,
+ a2.project,
+ a2.dateOnly,
+ a2.enddateCoalesced
+ FROM study.assignment a2
+ WHERE a2.qcstate.publicdata = TRUE
+ -- NOTE: We don't exclude 1-day assignments or treat them differently.
+ -- AND a2.duration > 1
+ ) a2 ON (
+ i2.id = a2.id
+ AND a2.dateOnly <= i2.dateOnly
+ AND a.project != a2.project
+ -- Assignments end at midnight, so an assignment doesn't count on the current date if it ends on it.
+ -- However, we also include 1-day assignments, which *can* have the end date match the start date.
+ AND (a2.enddate IS NULL OR a2.enddateCoalesced > i2.dateOnly OR (a2.dateOnly = i2.dateOnly AND a2.enddateCoalesced = i2.dateOnly))
+ AND a.lsid != a2.lsid
+ )
+
+ -- Find overlapping TMB on this date, which overrides the per diem.
+ LEFT JOIN study.assignment tmb ON (
+ a.id = tmb.id
+ AND tmb.dateOnly <= i2.dateOnly
+ AND tmb.project != a.project
+ AND tmb.endDateCoalesced >= i2.dateOnly
+ AND tmb.project.name = javaConstant('org.labkey.onprc_ehr.ONPRC_EHRManager.TMB_PROJECT')
+ )
+
+ LEFT JOIN onprc_billing.perDiemFeeDefinition pdf ON (
+ pdf.housingType = h3.room.housingType
+ AND pdf.housingDefinition = h3.room.housingCondition
+ -- Find overlapping tier flags on that day
+ AND coalesce(
+ (SELECT group_concat(DISTINCT f.flag.value) as tier
+ FROM study.flags f
+ --NOTE: allow flags that ended on this date
+ WHERE f.Id = i2.Id AND f.enddateCoalesced >= i2.dateOnly AND f.dateOnly <= i2.dateOnly AND f.flag.category = 'Housing Tier'),
+ 'Tier 2'
+ ) = pdf.tier
+ )
+
+ GROUP BY i2.dateOnly, I2.Id, a.project, a.project.use_Category, i2.researchRecordCount, i2.bottleFedRecordCount
) t
\ No newline at end of file
diff --git a/onprc_ehr/resources/etls/CageAuditLog.xml b/onprc_ehr/resources/etls/CageAuditLog.xml
new file mode 100644
index 000000000..b6d1068cc
--- /dev/null
+++ b/onprc_ehr/resources/etls/CageAuditLog.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ CageAuditLog
+ Executes onprc_ehr.p_CageAuditHistoryProcess daily (no load)
+
+
+
+ Run stored proc p_CageAuditHistoryProcess
+
+
+
+
+
+
+
+
+
+
diff --git a/onprc_ehr/resources/queries/study/parentageSummary.sql b/onprc_ehr/resources/queries/study/parentageSummary.sql
index 9eecf38cc..d410bee51 100644
--- a/onprc_ehr/resources/queries/study/parentageSummary.sql
+++ b/onprc_ehr/resources/queries/study/parentageSummary.sql
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2017 LabKey Corporation
+ * Copyright (c) 2013-2016 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,9 +21,9 @@ SELECT
p.method
FROM study.parentage p
-WHERE p.qcstate.publicdata = true and p.enddateCoalesced <= now()
+WHERE p.qcstate.publicdata = true and p.enddate is null
-UNION ALL
+UNION
SELECT
b.Id,
@@ -34,15 +34,16 @@ SELECT
FROM study.birth b
WHERE b.dam is not null and b.qcstate.publicdata = true
-
-UNION ALL
+And 'dam' not in (select k.relationship from study.parentage k where k.Id = b.Id and k.enddate is null)
+UNION
SELECT
- b.Id,
- b.date,
- b.sire,
+ a.Id,
+ a.date,
+ a.sire,
'Sire' as relationship,
'Observed' as method
-FROM study.birth b
-WHERE b.sire is not null and b.qcstate.publicdata = true
\ No newline at end of file
+FROM study.birth a
+WHERE a.sire is not null and a.qcstate.publicdata = true
+ And 'sire' not in (select k.relationship from study.parentage k where k.Id = a.Id and k.enddate is null)
\ No newline at end of file
diff --git a/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js b/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js
index ff0d25cfe..d6f8da6ac 100644
--- a/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js
+++ b/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js
@@ -568,59 +568,6 @@ exports.init = function(EHR){
}
});
- EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'assignment', function(helper, scriptErrors, row, oldRow){
- // note: if this is automatically generated from death/departure, allow an incomplete record
- // alerts will flag these
- if (row.enddate && !row.releaseCondition && !helper.isGeneratedByServer()){
- EHR.Server.Utils.addError(scriptErrors, 'releaseCondition', 'Must provide the release condition when the release date is set', 'WARN');
- }
-
- if (row.enddate && !row.releaseType && !helper.isGeneratedByServer()){
- EHR.Server.Utils.addError(scriptErrors, 'releaseType', 'Must provide the release type when the release date is set', 'WARN');
- }
-
- //update condition on release
- //Modified: 5-13-2019 R.Blasa
- if (!helper.isETL() && helper.getEvent() == 'update' && oldRow){
- if (EHR.Server.Security.getQCStateByLabel(row.QCStateLabel).PublicData && EHR.Server.Security.getQCStateByLabel(oldRow.QCStateLabel).PublicData){
- if (row.releaseCondition && row.enddate && row.releaseCondition != 206){
- var msg = triggerHelper.checkForConditionDowngrade(row.Id, row.enddate, row.releaseCondition);
- if (msg){
- EHR.Server.Utils.addError(scriptErrors, 'releaseCondition', msg, 'INFO');
- }
- else {
- triggerHelper.updateAnimalCondition(row.Id, row.enddate, row.releaseCondition);
- }
- }
- }
- }
-
- // we want to record the date a record was marked endded, in addition to the actual end itself
- // NOTE: we only do this when both enddate and releaseType are entered
- if (!row.enddatefinalized && row.enddate && row.releaseCondition && EHR.Server.Security.getQCStateByLabel(row.QCStateLabel).PublicData){
- //note: if ended in the future, defer to that date
- row.enddatefinalized = new Date();
- if (row.enddate.getTime() > row.enddatefinalized.getTime()){
- row.enddatefinalized = row.enddate;
- }
- }
-
- //check for condition downgrade for assign condition
- if (!helper.isETL() && row.Id && row.assignCondition){
- var msg = triggerHelper.checkForConditionDowngrade(row.Id, row.date, row.assignCondition);
- if (msg){
- EHR.Server.Utils.addError(scriptErrors, 'assignCondition', msg, 'INFO');
- }
- }
-
- //check for condition downgrade for assign condition
- if (!helper.isETL() && row.Id && row.date && row.assignCondition){
- var msg = triggerHelper.checkForConditionDowngrade(row.Id, row.date, row.assignCondition);
- if (msg){
- EHR.Server.Utils.addError(scriptErrors, 'assignCondition', msg, 'INFO');
- }
- }
- });
EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.ON_BECOME_PUBLIC, 'study', 'assignment', function(scriptErrors, helper, row, oldRow){
//Modified: 5-9-2019 R.Blasa Prevent flag enttrie for terminal monkey ids
@@ -1307,6 +1254,86 @@ exports.init = function(EHR){
}
}
});
+ // Added 10-17-2025 R. Blasa
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'assignment', EHR.Server.TriggerManager.Events.BEFORE_UPSERT);
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'assignment', function(helper, scriptErrors, row, oldRow){
+ if (!helper.isETL()){
+ //note: the the date field is handled above by removeTimeFromDate
+ EHR.Server.Utils.removeTimeFromDate(row, scriptErrors, 'enddate');
+ EHR.Server.Utils.removeTimeFromDate(row, scriptErrors, 'projectedRelease');
+ }
+
+
+ //check number of allowed animals at assign/approve time
+ if (!helper.isETL() && !helper.isQuickValidation() && helper.doStandardProtocolCountValidation() &&
+ //this is designed to always perform the check on imports, but also updates where the Id was changed
+ !(oldRow && oldRow.Id && oldRow.Id==row.Id) &&
+ row.Id && row.project && row.date
+ ){
+ var assignmentsInTransaction = helper.getProperty('assignmentsInTransaction');
+ assignmentsInTransaction = assignmentsInTransaction || [];
+
+ var msgs = helper.getJavaHelper().verifyProtocolCounts(row.Id, row.project, assignmentsInTransaction);
+ if (msgs){
+ msgs = msgs.split("<>");
+ for (var i=0;i row.enddatefinalized.getTime()){
+ row.enddatefinalized = row.enddate;
+ }
+ }
+
+ //check for condition downgrade for assign condition
+ if (!helper.isETL() && row.Id && row.assignCondition){
+ var msg = triggerHelper.checkForConditionDowngrade(row.Id, row.date, row.assignCondition);
+ if (msg){
+ EHR.Server.Utils.addError(scriptErrors, 'assignCondition', msg, 'INFO');
+ }
+ }
+
+ //check for condition downgrade for assign condition
+ if (!helper.isETL() && row.Id && row.date && row.assignCondition){
+ var msg = triggerHelper.checkForConditionDowngrade(row.Id, row.date, row.assignCondition);
+ if (msg){
+ EHR.Server.Utils.addError(scriptErrors, 'assignCondition', msg, 'INFO');
+ }
+ }
+ });
//Added 10-5-2022 R.Blasa
EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'matings', function (helper, scriptErrors, row, oldRow) {
diff --git a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/AbstractGenericONPRC_EHRTest.java b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/AbstractGenericONPRC_EHRTest.java
index 171787cc7..6bcbe2d84 100644
--- a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/AbstractGenericONPRC_EHRTest.java
+++ b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/AbstractGenericONPRC_EHRTest.java
@@ -394,6 +394,7 @@ protected String ensureFlagExists(final String category, final String name, fina
SelectRowsCommand select1 = new SelectRowsCommand("ehr_lookups", "flag_values");
select1.addFilter(new Filter("category", category, Filter.Operator.EQUAL));
select1.addFilter(new Filter("value", name, Filter.Operator.EQUAL));
+ select1.addFilter(new Filter("datedisabled", null, Filter.Operator.ISBLANK));
SelectRowsResponse resp = select1.execute(getApiHelper().getConnection(), getContainerPath());
String objectid = resp.getRowCount().intValue() == 0 ? null : (String)resp.getRows().get(0).get("objectid");
diff --git a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java
index c155344ab..e1313a66f 100644
--- a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java
+++ b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java
@@ -43,6 +43,7 @@
import org.labkey.test.components.BodyWebPart;
import org.labkey.test.pages.ehr.AnimalHistoryPage;
import org.labkey.test.pages.ehr.EnterDataPage;
+import org.labkey.test.pages.ehr.NotificationAdminPage;
import org.labkey.test.util.DataRegionTable;
import org.labkey.test.util.Ext4Helper;
import org.labkey.test.util.LogMethod;
@@ -71,6 +72,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -1975,5 +1977,104 @@ protected String getAnimalHistoryPath()
{
return ANIMAL_HISTORY_URL;
}
-}
+ @Test
+ public void testNotificationAdminAudits() throws Exception
+ {
+ // Ensure notification service is configured
+ setupNotificationService();
+
+ // Navigate to Notification Admin page
+ goToEHRFolder();
+ waitAndClickAndWait(Locators.bodyPanel().append(Locator.tagContainingText("a", "EHR Admin Page")));
+ waitAndClickAndWait(Locator.tagContainingText("a", "Notification Admin"));
+
+ // Record start time for audit filtering
+ LocalDateTime start = LocalDateTime.now().minusMinutes(5);
+
+ // Change Notification User and Reply Email, then save
+ NotificationAdminPage.beginAt(this);
+ String replyEmail = "ehr-notify@labkey.test";
+ Ext4FieldRef.getForLabel(this, "Notification User").setValue(PasswordUtil.getUsername());
+ Ext4FieldRef.getForLabel(this, "Reply Email").setValue(replyEmail);
+ click(Ext4Helper.Locators.ext4Button("Save"));
+ waitForElement(Ext4Helper.Locators.window("Success"));
+ waitAndClickAndWait(Ext4Helper.Locators.ext4Button("OK"));
+
+ // Identify two notifications by extracting their keys from the Run Report links
+ beginAt(WebTestHelper.getBaseURL() + "/ldk/" + getContainerPath() + "/notificationAdmin.view");
+ Locator links = Locator.tagContainingText("a", "Run Report In Browser");
+ waitFor(() -> links.findElements(getDriver()).size() >= 2, "Expected at least two notifications to be available", WAIT_FOR_PAGE);
+ List runLinks = links.findElements(getDriver());
+
+ List notifKeys = new ArrayList<>();
+ for (int i = 0; i < Math.min(2, runLinks.size()); i++)
+ {
+ String href = runLinks.get(i).getAttribute("href");
+ int idx = href.indexOf("key=");
+ Assert.assertTrue("Could not locate notification key in href: " + href, idx > -1);
+ String key = href.substring(idx + 4);
+ notifKeys.add(key);
+ }
+
+ // Disable the two notifications and save
+ for (String key : notifKeys)
+ {
+ _ext4Helper.selectComboBoxItem(Ext4Helper.Locators.formItemWithInputNamed("status_" + key), Ext4Helper.TextMatchTechnique.EXACT, "Disabled");
+ }
+ click(Ext4Helper.Locators.ext4Button("Save"));
+ waitForElement(Ext4Helper.Locators.window("Success"));
+ waitAndClickAndWait(Ext4Helper.Locators.ext4Button("OK"));
+
+ // Re-enable the two notifications and save
+ for (String key : notifKeys)
+ {
+ _ext4Helper.selectComboBoxItem(Ext4Helper.Locators.formItemWithInputNamed("status_" + key), Ext4Helper.TextMatchTechnique.EXACT, "Enabled");
+ }
+ click(Ext4Helper.Locators.ext4Button("Save"));
+ waitForElement(Ext4Helper.Locators.window("Success"));
+ waitAndClickAndWait(Ext4Helper.Locators.ext4Button("OK"));
+
+ // Manage subscribed users on the first notification: add two users then remove one
+ Locator manageLink = Locator.tagContainingText("a", "Manage Subscribed Users/Groups").index(0);
+ waitAndClick(manageLink);
+ waitForElement(Ext4Helper.Locators.window("Manage Subscribed Users"));
+ Ext4ComboRef combo = Ext4ComboRef.getForLabel(this, "Add User Or Group");
+ combo.waitForStoreLoad();
+ _ext4Helper.selectComboBoxItem(Locator.id(combo.getId()), Ext4Helper.TextMatchTechnique.CONTAINS, DATA_ADMIN.getEmail());
+ Ext4FieldRef.waitForComponent(this, "field[fieldLabel^='Add User Or Group']");
+ combo = Ext4ComboRef.getForLabel(this, "Add User Or Group");
+ _ext4Helper.selectComboBoxItem(Locator.id(combo.getId()), Ext4Helper.TextMatchTechnique.CONTAINS, BASIC_SUBMITTER.getEmail());
+ waitAndClick(Ext4Helper.Locators.ext4Button("Close"));
+
+ // Re-open and remove one user
+ waitAndClick(manageLink);
+ waitForElement(Ext4Helper.Locators.window("Manage Subscribed Users"));
+ assertElementPresent(Ext4Helper.Locators.ext4Button("Remove"));
+ waitAndClick(Ext4Helper.Locators.ext4Button("Remove").index(0));
+ waitAndClick(Ext4Helper.Locators.ext4Button("Close"));
+
+ // Verify audit log entries in audit.SiteSettings for this container
+ sleep(1000);
+
+ Function countAudit = (commentSubstring) -> {
+ try
+ {
+ SelectRowsCommand cmd = new SelectRowsCommand("auditLog", "AppPropsEvent");
+ cmd.addFilter(new Filter("Created", Date.from(start.atZone(ZoneId.systemDefault()).toInstant()), Filter.Operator.DATE_GTE));
+ cmd.addFilter(new Filter("Comment", commentSubstring, Filter.Operator.CONTAINS));
+ SelectRowsResponse resp = cmd.execute(getApiHelper().getConnection(), getContainerPath());
+ return resp.getRowCount().intValue();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Assert.assertTrue("Expected audit entry for reply-to email update", countAudit.apply("Notification reply-to email updated.") > 0);
+ Assert.assertTrue("Expected audit entry for service user update", countAudit.apply("Notification service user updated.") > 0);
+ Assert.assertTrue("Expected audit entry for notification enabled", countAudit.apply("has been enabled.") > 0);
+ Assert.assertTrue("Expected audit entry for subscription updates", countAudit.apply("Updated notification subscriptions for") > 0);
+ }
+}