[med-svn] [Git][med-team/gnumed-server][master] 5 commits: routine-update: New upstream version

Andreas Tille (@tille) gitlab at salsa.debian.org
Sun Aug 29 14:25:46 BST 2021



Andreas Tille pushed to branch master at Debian Med / gnumed-server


Commits:
5a0b40d1 by Andreas Tille at 2021-08-29T15:22:07+02:00
routine-update: New upstream version

- - - - -
1f27a818 by Andreas Tille at 2021-08-29T15:22:08+02:00
New upstream version 22.16
- - - - -
cfcb3672 by Andreas Tille at 2021-08-29T15:23:04+02:00
Update upstream source from tag 'upstream/22.16'

Update to upstream version '22.16'
with Debian dir 0788a856496e64f0c5ff25a14985c9ab7dcd0289
- - - - -
99946d54 by Andreas Tille at 2021-08-29T15:23:05+02:00
routine-update: Standards-Version: 4.6.0

- - - - -
d23cc38f by Andreas Tille at 2021-08-29T15:25:09+02:00
routine-update: Ready to upload to unstable

- - - - -


20 changed files:

- debian/changelog
- debian/control
- server/bootstrap/bootstrap_gm_db_system.py
- server/bootstrap/fixup_db-v22.conf
- server/bootstrap/update_db-v21_v22.conf
- server/bootstrap/upgrade-db.sh
- server/doc/schema/gnumed-entire_schema.html
- server/gm-backup.sh
- server/gm-restore.sh
- server/gm-zip+sign_backups.sh
- server/pycommon/gmBackendListener.py
- server/pycommon/gmConnectionPool.py
- server/pycommon/gmDateTime.py
- server/pycommon/gmI18N.py
- server/pycommon/gmNetworkTools.py
- server/pycommon/gmPG2.py
- server/pycommon/gmTools.py
- server/sql/v21-v22/dynamic/v22-clin-remove_old_empty_encounters.sql
- + server/sql/v21-v22/fixups/v22-clin-remove_old_empty_encounters-fixup.sql
- server/sql/v21-v22/fixups/v22-release_notes-fixup.sql


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+gnumed-server (22.16-1) unstable; urgency=medium
+
+  * New upstream version
+  * Standards-Version: 4.6.0 (routine-update)
+
+ -- Andreas Tille <tille at debian.org>  Sun, 29 Aug 2021 15:23:21 +0200
+
 gnumed-server (22.15-1) unstable; urgency=medium
 
   * New upstream version


=====================================
debian/control
=====================================
@@ -8,7 +8,7 @@ Build-Depends: debhelper-compat (= 13),
                dh-python,
                python3
 Build-Depends-Indep: po-debconf
-Standards-Version: 4.5.1
+Standards-Version: 4.6.0
 Vcs-Browser: https://salsa.debian.org/med-team/gnumed-server
 Vcs-Git: https://salsa.debian.org/med-team/gnumed-server.git
 Homepage: https://www.gnumed.de


=====================================
server/bootstrap/bootstrap_gm_db_system.py
=====================================
@@ -39,6 +39,9 @@ __license__ = "GPL v2 or later"
 
 # standard library
 import sys
+if sys.hexversion < 0x3000000:
+	sys.exit('This code must be run with Python 3.')
+
 import os.path
 import fileinput
 import os
@@ -81,7 +84,11 @@ sys.path.insert(0, local_python_base_dir)
 
 
 # GNUmed imports
-from Gnumed.pycommon import gmLog2
+try:
+	from Gnumed.pycommon import gmLog2
+except ImportError:
+	print("""Please make sure the GNUmed Python modules are in the Python path !""")
+	raise
 from Gnumed.pycommon import gmCfg2
 from Gnumed.pycommon import gmPsql
 from Gnumed.pycommon import gmPG2
@@ -1902,7 +1909,7 @@ sys.exit(0)
 #	pipe.tochild.close()
 
 #	result = pipe.wait()
-#	print result
+#	print(result)
 
 	# read any leftovers
 #	pipe.fromchild.flush()


=====================================
server/bootstrap/fixup_db-v22.conf
=====================================
@@ -33,6 +33,7 @@ v22-i18n-lang_funcs-fixup.sql
 v22-clin-v_candidate_diagnoses-fixup.sql
 v22-dem-v_message_inbox-fixup.sql
 v22-add_generic_covid_vaccine.sql
+v22-clin-remove_old_empty_encounters-fixup.sql
 v22-release_notes-fixup.sql
 $schema$
 


=====================================
server/bootstrap/update_db-v21_v22.conf
=====================================
@@ -165,6 +165,7 @@ v22-i18n-lang_funcs-fixup.sql
 v22-clin-v_candidate_diagnoses-fixup.sql
 v22-dem-v_message_inbox-fixup.sql
 v22-add_generic_covid_vaccine.sql
+v22-clin-remove_old_empty_encounters-fixup.sql
 v22-release_notes-fixup.sql
 $schema$
 


=====================================
server/bootstrap/upgrade-db.sh
=====================================
@@ -85,7 +85,6 @@ function echo_msg () {
 }
 
 
-
 # Darwin/MacOSX ?
 # (MacOSX cannot "su -c" ...)
 SYSTEM=`uname -s`
@@ -110,7 +109,6 @@ if test "${SYSTEM}" != "Darwin" ; then
 fi ;
 
 
-
 # show what we do
 echo_msg ""
 echo_msg "==========================================================="
@@ -131,7 +129,7 @@ echo_msg "==========================================================="
 
 
 
-# better safe than sorry
+# check for existing target database
 if test "${SYSTEM}" != "Darwin" ; then
 	# Does TARGET database exist ?
 	VER_EXISTS=`su -c "psql -l ${PORT_DEF}" -l postgres | grep gnumed_v${NEXT_VER}`
@@ -153,12 +151,35 @@ if test "${SYSTEM}" != "Darwin" ; then
 			echo "Upgrading aborted by user."
 			exit 1
 		fi
+		echo ""
 	fi
 fi
 
 
+# check disk space
+DATA_DIR=$(su -c "psql --no-align --tuples-only --quiet -c \"SELECT setting FROM pg_settings WHERE name = 'data_directory' \" " -l postgres)
+BYTES_FREE=$(df --block-size=1 --output=avail ${DATA_DIR} | grep --only-matching -E '[[:digit:]]+')
+DB_SIZE=$(su -c "psql --no-align --tuples-only --quiet -c \"SELECT pg_database_size('gnumed_v22') \" | grep --only-matching -E '[[:digit:]]+' " -l postgres)	#"
+if [ ${DB_SIZE} -gt ${BYTES_FREE} ] ; then
+	echo ""
+	echo "WARNING: Disk space may be insufficient"
+	echo "WARNING:"
+	echo "WARNING:  Data directory : ${DATA_DIR}"
+	echo "WARNING:  Data directory : ${DB_SIZE}"
+	echo "WARNING:  Free disk space: ${BYTES_FREE}"
+	echo "WARNING:"
+	echo ""
+	echo "Continue upgrading (may fail) ? "
+	echo ""
+	read -e -p "[y / N]: "
+	if test "${REPLY}" != "y" ; then
+		echo "Upgrading aborted by user."
+		exit 1
+	fi
+fi
+
 
-# eventually attempt the upgrade
+# either backup or verify checksums
 echo_msg ""
 if test "$SKIP_BACKUP" != "no-backup" ; then
 	echo_msg "1) creating backup of the database that is to be upgraded ..."
@@ -218,6 +239,7 @@ else
 fi ;
 
 
+# eventually attempt the upgrade
 echo_msg ""
 echo_msg "2) upgrading to new database ..."
 # fixup for schema hash function


=====================================
server/doc/schema/gnumed-entire_schema.html
=====================================
@@ -112,7 +112,7 @@
   <body>
 
     <!-- Primary Index -->
-	<p><br><br>Dumped on 2021-01-23</p>
+	<p><br><br>Dumped on 2021-08-02</p>
 <h1><a name="index">Index of database - gnumed_v22</a></h1>
 <ul>
     
@@ -103018,6 +103018,9 @@ BEGIN
 	DELETE FROM clin.encounter WHERE
 		clin.encounter.fk_patient = _pk_identity
 			AND
+		-- is this the active encounter somewhere ?
+		(SELECT pg_try_advisory_lock('clin.encounter'::regclass::oid::int, clin.encounter.pk))
+			AND
 		age(clin.encounter.last_affirmed) > _usable_minimum_encounter_age
 			AND
 		NOT EXISTS (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
@@ -103036,7 +103039,7 @@ BEGIN
 			AND
 		NOT EXISTS (SELECT 1 FROM clin.suppressed_hint WHERE fk_encounter = clin.encounter.pk)
 	;
-	return true;
+	RETURN TRUE;
 END;</pre>
 	
 		<hr>
@@ -116594,25 +116597,174 @@ SELECT all_msgs.received_when
 FROM (
      (
            (
+            SELECT mi.modified_when AS received_when
+                 ,
+            COALESCE
+                 (
+                       (
+                        SELECT staff.short_alias
+                   
+                          FROM dem.staff
+                  
+                         WHERE (staff.db_user = mi.modified_by)
+                       )
+                       , (
+                             ('<'::text || 
+                                   (mi.modified_by)::text
+                             ) || '>'::text
+                       )
+                 ) AS modified_by
+                 ,
+            
+                 (
+                  SELECT staff.short_alias
+                   
+                    FROM dem.staff
+                  
+                   WHERE (staff.pk = mi.fk_staff)
+                 ) AS provider
+                 ,
+            mi.importance
+                 ,
+            vit.category
+                 ,
+            vit.l10n_category
+                 ,
+            vit.type
+                 ,
+            vit.l10n_type
+                 ,
+            mi.comment
+                 ,
+            mi.ufk_context AS pk_context
+                 ,
+            mi.data
+                 ,
+            mi.pk AS pk_inbox_message
+                 ,
+            mi.fk_staff AS pk_staff
+                 ,
+            vit.pk_category
+                 ,
+            mi.fk_inbox_item_type AS pk_type
+                 ,
+            mi.fk_patient AS pk_patient
+                 ,
+            false AS is_virtual
+                 ,
+            mi.due_date
+                 ,
+            mi.expiry_date
+                 ,
+                CASE
+                    WHEN 
+                 (mi.due_date IS NULL) THEN false
+                    WHEN 
+                 (mi.due_date > now
+                       ()
+                 ) THEN false
+                    WHEN 
+                 (mi.expiry_date IS NULL) THEN true
+                    WHEN 
+                 (mi.expiry_date < now
+                       ()
+                 ) THEN false
+                    ELSE true
+                END AS is_overdue
+                 ,
+                CASE
+                    WHEN 
+                 (mi.expiry_date IS NULL) THEN false
+                    WHEN 
+                 (mi.expiry_date > now
+                       ()
+                 ) THEN false
+                    ELSE true
+                END AS is_expired
+                 ,
+                CASE
+                    WHEN 
+                 (mi.due_date IS NULL) THEN NULL::interval
+                    WHEN 
+                 (mi.due_date > now
+                       ()
+                 ) THEN 
                  (
+                       (mi.due_date)::timestamp with time zone - now
+                       ()
+                 )
+                    ELSE 
+                 (now
+                       () - 
+                       (mi.due_date)::timestamp with time zone
+                 )
+                END AS interval_due
+                 ,
+            gm.xid2int
+                 (mi.xmin) AS xmin_message_inbox
+           
+              FROM dem.message_inbox mi
+                 ,
+            dem.v_inbox_item_type vit
+          
+             WHERE (mi.fk_inbox_item_type = vit.pk_type)
+        
+         UNION ALL
+         
+            SELECT v_unreviewed_docs_inbox.received_when
+                 ,
+            v_unreviewed_docs_inbox.modified_by
+                 ,
+            v_unreviewed_docs_inbox.provider
+                 ,
+            v_unreviewed_docs_inbox.importance
+                 ,
+            v_unreviewed_docs_inbox.category
+                 ,
+            v_unreviewed_docs_inbox.l10n_category
+                 ,
+            v_unreviewed_docs_inbox.type
+                 ,
+            v_unreviewed_docs_inbox.l10n_type
+                 ,
+            v_unreviewed_docs_inbox.comment
+                 ,
+            v_unreviewed_docs_inbox.pk_context
+                 ,
+            v_unreviewed_docs_inbox.data
+                 ,
+            v_unreviewed_docs_inbox.pk_inbox_message
+                 ,
+            v_unreviewed_docs_inbox.pk_staff
+                 ,
+            v_unreviewed_docs_inbox.pk_category
+                 ,
+            v_unreviewed_docs_inbox.pk_type
+                 ,
+            v_unreviewed_docs_inbox.pk_patient
+                 ,
+            v_unreviewed_docs_inbox.is_virtual
+                 ,
+            v_unreviewed_docs_inbox.due_date
+                 ,
+            v_unreviewed_docs_inbox.expiry_date
+                 ,
+            v_unreviewed_docs_inbox.is_overdue
+                 ,
+            v_unreviewed_docs_inbox.is_expired
+                 ,
+            v_unreviewed_docs_inbox.interval_due
+                 ,
+            v_unreviewed_docs_inbox.xmin_message_inbox
+           
+              FROM blobs.v_unreviewed_docs_inbox
+        
+         UNION ALL (
                  
-                  SELECT mi.modified_when AS received_when
+                  SELECT now
+                       () AS received_when
                        ,
-                    COALESCE
-                       (
-                             (
-                              SELECT staff.short_alias
-                           
-                                FROM dem.staff
-                          
-                               WHERE (staff.db_user = mi.modified_by)
-                             )
-                             , (
-                                   ('<'::text || 
-                                         (mi.modified_by)::text
-                                   ) || '>'::text
-                             )
-                       ) AS modified_by
+                    vtr.modified_by
                        ,
                     
                        (
@@ -116620,174 +116772,132 @@ FROM (
                            
                           FROM dem.staff
                           
-                         WHERE (staff.pk = mi.fk_staff)
+                         WHERE (staff.pk = vtr.pk_intended_reviewer)
                        ) AS provider
                        ,
-                    mi.importance
-                       ,
-                    vit.category
-                       ,
-                    vit.l10n_category
-                       ,
-                    vit.type
-                       ,
-                    vit.l10n_type
-                       ,
-                    mi.comment
-                       ,
-                    mi.ufk_context AS pk_context
-                       ,
-                    mi.data
-                       ,
-                    mi.pk AS pk_inbox_message
+                    1 AS importance
                        ,
-                    mi.fk_staff AS pk_staff
+                    'clinical'::text AS category
                        ,
-                    vit.pk_category
+                    _
+                       ('clinical'::text) AS l10n_category
                        ,
-                    mi.fk_inbox_item_type AS pk_type
+                    'review results'::text AS type
                        ,
-                    mi.fk_patient AS pk_patient
+                    _
+                       ('review results'::text) AS l10n_type
                        ,
-                    false AS is_virtual
-                       ,
-                    mi.due_date
-                       ,
-                    mi.expiry_date
-                       ,
-                        CASE
-                            WHEN 
-                       (mi.due_date IS NULL) THEN false
-                            WHEN 
-                       (mi.due_date > now
-                             ()
-                       ) THEN false
-                            WHEN 
-                       (mi.expiry_date IS NULL) THEN true
-                            WHEN 
-                       (mi.expiry_date < now
-                             ()
-                       ) THEN false
-                            ELSE true
-                        END AS is_overdue
-                       ,
-                        CASE
-                            WHEN 
-                       (mi.expiry_date IS NULL) THEN false
-                            WHEN 
-                       (mi.expiry_date > now
-                             ()
-                       ) THEN false
-                            ELSE true
-                        END AS is_expired
-                       ,
-                        CASE
-                            WHEN 
-                       (mi.due_date IS NULL) THEN NULL::interval
-                            WHEN 
-                       (mi.due_date > now
-                             ()
-                       ) THEN 
+                    
                        (
-                             (mi.due_date)::timestamp with time zone - now
-                             ()
+                        SELECT (
+                                   (
+                                         (
+                                               (
+                                                     (_
+                                                           ('unreviewed (abnormal) results for patient'::text
+                                                     ) || ' ['::text
+                                               ) || dn.lastnames
+                                         ) || 
+                                      ', '::text
+                                   ) || dn.firstnames
+                             ) || ']'::text
                        )
-                            ELSE 
-                       (now
-                             () - 
-                             (mi.due_date)::timestamp with time zone
+                           
+                    FROM dem.names dn
+                          
+                   WHERE (
+                             (dn.id_identity = vtr.pk_patient)
+                           AND (dn.active IS TRUE)
                        )
-                        END AS interval_due
-                       ,
-                    gm.xid2int
-                       (mi.xmin) AS xmin_message_inbox
+                 ) AS comment
+                 ,
+                    NULL::integer[] AS pk_context
+                 ,
+                    NULL::text AS data
+                 ,
+                    NULL::integer AS pk_inbox_message
+                 ,
+                    vtr.pk_intended_reviewer AS pk_staff
+                 ,
+                    
+                 (
+                  SELECT v_inbox_item_type.pk_category
+                           
+                    FROM dem.v_inbox_item_type
+                          
+                   WHERE (v_inbox_item_type.type = 'review results'::text)
+                 ) AS pk_category
+                 ,
+                    
+                 (
+                  SELECT v_inbox_item_type.pk_type
+                           
+                    FROM dem.v_inbox_item_type
+                          
+                   WHERE (v_inbox_item_type.type = 'review results'::text)
+                 ) AS pk_type
+                 ,
+                    vtr.pk_patient
+                 ,
+                    true AS is_virtual
+                 ,
+                    
+                 (now
+                       () - '01:00:00'::interval
+                 ) AS due_date
+                 ,
+                    NULL::timestamp with time zone AS expiry_date
+                 ,
+                    true AS is_overdue
+                 ,
+                    false AS is_expired
+                 ,
+                    '01:00:00'::interval AS interval_due
+                 ,
+                    NULL::integer AS xmin_message_inbox
                    
-                    FROM dem.message_inbox mi
-                       ,
-                    dem.v_inbox_item_type vit
+              FROM clin.v_test_results vtr
                   
-                   WHERE (mi.fk_inbox_item_type = vit.pk_type)
-                
-               UNION ALL
-                 
-                  SELECT v_unreviewed_docs_inbox.received_when
-                       ,
-                    v_unreviewed_docs_inbox.modified_by
-                       ,
-                    v_unreviewed_docs_inbox.provider
-                       ,
-                    v_unreviewed_docs_inbox.importance
-                       ,
-                    v_unreviewed_docs_inbox.category
-                       ,
-                    v_unreviewed_docs_inbox.l10n_category
-                       ,
-                    v_unreviewed_docs_inbox.type
-                       ,
-                    v_unreviewed_docs_inbox.l10n_type
-                       ,
-                    v_unreviewed_docs_inbox.comment
-                       ,
-                    v_unreviewed_docs_inbox.pk_context
-                       ,
-                    v_unreviewed_docs_inbox.data
-                       ,
-                    v_unreviewed_docs_inbox.pk_inbox_message
-                       ,
-                    v_unreviewed_docs_inbox.pk_staff
-                       ,
-                    v_unreviewed_docs_inbox.pk_category
-                       ,
-                    v_unreviewed_docs_inbox.pk_type
-                       ,
-                    v_unreviewed_docs_inbox.pk_patient
-                       ,
-                    v_unreviewed_docs_inbox.is_virtual
-                       ,
-                    v_unreviewed_docs_inbox.due_date
-                       ,
-                    v_unreviewed_docs_inbox.expiry_date
-                       ,
-                    v_unreviewed_docs_inbox.is_overdue
-                       ,
-                    v_unreviewed_docs_inbox.is_expired
-                       ,
-                    v_unreviewed_docs_inbox.interval_due
-                       ,
-                    v_unreviewed_docs_inbox.xmin_message_inbox
-                   
-                    FROM blobs.v_unreviewed_docs_inbox
-        
+             WHERE (
+                       (vtr.reviewed IS FALSE)
+                     AND (
+                             (vtr.is_technically_abnormal IS TRUE)
+                            OR (
+                                   (vtr.is_technically_abnormal IS NULL)
+                                 AND (vtr.abnormality_indicator IS NOT NULL)
+                             )
+                       )
                  )
+                
              UNION
-         
+                 
             SELECT now
                  () AS received_when
                  ,
-            vtr.modified_by
+                    vtr.modified_by
                  ,
-            
+                    
                  (
                   SELECT staff.short_alias
-                   
+                           
                     FROM dem.staff
-                  
+                          
                    WHERE (staff.pk = vtr.pk_intended_reviewer)
                  ) AS provider
                  ,
-            0 AS importance
+                    0 AS importance
                  ,
-            'clinical'::text AS category
+                    'clinical'::text AS category
                  ,
-            _
+                    _
                  ('clinical'::text) AS l10n_category
                  ,
-            'review results'::text AS type
+                    'review results'::text AS type
                  ,
-            _
+                    _
                  ('review results'::text) AS l10n_type
                  ,
-            
+                    
                  (
                   SELECT (
                              (
@@ -116802,62 +116912,62 @@ FROM (
                              ) || dn.firstnames
                        ) || ']'::text
                  )
-                   
+                           
               FROM dem.names dn
-                  
+                          
              WHERE (
                        (dn.id_identity = vtr.pk_patient)
                      AND (dn.active IS TRUE)
                  )
            ) AS comment
            ,
-            NULL::integer[] AS pk_context
+                    NULL::integer[] AS pk_context
            ,
-            NULL::text AS data
+                    NULL::text AS data
            ,
-            NULL::integer AS pk_inbox_message
+                    NULL::integer AS pk_inbox_message
            ,
-            vtr.pk_intended_reviewer AS pk_staff
+                    vtr.pk_intended_reviewer AS pk_staff
            ,
-            
+                    
            (
             SELECT v_inbox_item_type.pk_category
-                   
+                           
               FROM dem.v_inbox_item_type
-                  
+                          
              WHERE (v_inbox_item_type.type = 'review results'::text)
            ) AS pk_category
            ,
-            
+                    
            (
             SELECT v_inbox_item_type.pk_type
-                   
+                           
               FROM dem.v_inbox_item_type
-                  
+                          
              WHERE (v_inbox_item_type.type = 'review results'::text)
            ) AS pk_type
            ,
-            vtr.pk_patient
+                    vtr.pk_patient
            ,
-            true AS is_virtual
+                    true AS is_virtual
            ,
-            
+                    
            (now
                  () - '01:00:00'::interval
            ) AS due_date
            ,
-            NULL::timestamp with time zone AS expiry_date
+                    NULL::timestamp with time zone AS expiry_date
            ,
-            true AS is_overdue
+                    true AS is_overdue
            ,
-            false AS is_expired
+                    false AS is_expired
            ,
-            '01:00:00'::interval AS interval_due
+                    '01:00:00'::interval AS interval_due
            ,
-            NULL::integer AS xmin_message_inbox
-           
+                    NULL::integer AS xmin_message_inbox
+                   
         FROM clin.v_test_results vtr
-          
+                  
        WHERE (
                  (vtr.reviewed IS FALSE)
                AND (
@@ -116869,114 +116979,6 @@ FROM (
                  )
            )
         
-       UNION
-         
-      SELECT now
-           () AS received_when
-           ,
-            vtr.modified_by
-           ,
-            
-           (
-            SELECT staff.short_alias
-                   
-              FROM dem.staff
-                  
-             WHERE (staff.pk = vtr.pk_intended_reviewer)
-           ) AS provider
-           ,
-            1 AS importance
-           ,
-            'clinical'::text AS category
-           ,
-            _
-           ('clinical'::text) AS l10n_category
-           ,
-            'review results'::text AS type
-           ,
-            _
-           ('review results'::text) AS l10n_type
-           ,
-            
-           (
-            SELECT (
-                       (
-                             (
-                                   (
-                                         (_
-                                               ('unreviewed (abnormal) results for patient'::text
-                                         ) || ' ['::text
-                                   ) || dn.lastnames
-                             ) || 
-                          ', '::text
-                       ) || dn.firstnames
-                 ) || ']'::text
-           )
-                   
-        FROM dem.names dn
-                  
-       WHERE (
-                 (dn.id_identity = vtr.pk_patient)
-               AND (dn.active IS TRUE)
-           )
-     ) AS comment
-     ,
-            NULL::integer[] AS pk_context
-     ,
-            NULL::text AS data
-     ,
-            NULL::integer AS pk_inbox_message
-     ,
-            vtr.pk_intended_reviewer AS pk_staff
-     ,
-            
-     (
-      SELECT v_inbox_item_type.pk_category
-                   
-        FROM dem.v_inbox_item_type
-                  
-       WHERE (v_inbox_item_type.type = 'review results'::text)
-     ) AS pk_category
-     ,
-            
-     (
-      SELECT v_inbox_item_type.pk_type
-                   
-        FROM dem.v_inbox_item_type
-                  
-       WHERE (v_inbox_item_type.type = 'review results'::text)
-     ) AS pk_type
-     ,
-            vtr.pk_patient
-     ,
-            true AS is_virtual
-     ,
-            
-     (now
-           () - '01:00:00'::interval
-     ) AS due_date
-     ,
-            NULL::timestamp with time zone AS expiry_date
-     ,
-            true AS is_overdue
-     ,
-            false AS is_expired
-     ,
-            '01:00:00'::interval AS interval_due
-     ,
-            NULL::integer AS xmin_message_inbox
-           
-  FROM clin.v_test_results vtr
-          
- WHERE (
-           (vtr.reviewed IS FALSE)
-         AND (
-                 (vtr.is_technically_abnormal IS TRUE)
-                OR (
-                       (vtr.is_technically_abnormal IS NULL)
-                     AND (vtr.abnormality_indicator IS NOT NULL)
-                 )
-           )
      )
 ) all_msgs
      
@@ -116986,13 +116988,15 @@ ON (
 )
 )     
      
-JOIN   dem.names d_n 
+LEFT JOIN dem.names d_n 
 ON     (
 (d_i.pk = d_n.id_identity)
 )     
 )           
   
-WHERE        (d_n.active IS TRUE);</pre>
+WHERE        (d_n.active IS DISTINCT 
+FROM   false
+)           ;</pre>
 	
 
 	<!-- List off permissions -->


=====================================
server/gm-backup.sh
=====================================
@@ -65,7 +65,7 @@ fi
 # compute host argument and backup filename
 if test -z "${GM_HOST}" ; then
 	_PG_HOST_ARG=""
-	BACKUP_BASENAME="backup-${GM_DATABASE}-${INSTANCE_OWNER}-"$(hostname)
+	BACKUP_BASENAME="backup-${GM_DATABASE}-${INSTANCE_OWNER}-$(hostname)"
 else
 	if ping -c 3 -i 2 "${GM_HOST}" > /dev/null; then
 		_PG_HOST_ARG="--host=\"${GM_HOST}\""
@@ -108,7 +108,7 @@ fi
 if test "${HAS_HIGHER_VER}" = "t" ; then
 	echo "Backing up database ${GM_DATABASE}."
 	echo ""
-	echo "However, a newer database seems to exist:"
+	echo "However, a newer database seems to exist amongst the following:"
 	echo ""
 	sudo --user=postgres psql --no-psqlrc --list ${_PG_PORT_ARG} | grep gnumed_v
 	echo ""
@@ -123,13 +123,13 @@ BACKUP_FILENAME="${BACKUP_BASENAME}-${TS}"
 
 # generate scratch dir
 SCRATCH_DIR="/tmp/${BACKUP_FILENAME}"
-mkdir -p ${SCRATCH_DIR}
+mkdir -p "${SCRATCH_DIR}"
 RESULT="$?"
 if test "${RESULT}" != "0" ; then
 	echo "Cannot create backup scratch directory [${SCRATCH_DIR}] (${RESULT}). Aborting."
 	exit ${RESULT}
 fi
-cd ${SCRATCH_DIR}
+cd "${SCRATCH_DIR}"
 RESULT="$?"
 if test "${RESULT}" != "0" ; then
 	echo "Cannot change into scratch backup directory [${SCRATCH_DIR}] (${RESULT}). Aborting."
@@ -139,13 +139,13 @@ fi
 
 # create backup timestamp tag file
 TS_FILE="${BACKUP_BASENAME}-timestamp.txt"
-echo "backup: ${TS}" > ${TS_FILE}
+echo "backup: ${TS}" > "${TS_FILE}"
 
 
 # create dumps
 BACKUP_DATA_DIR="${BACKUP_BASENAME}.dir"
 # database
-pg_dump --verbose --format=directory --compress=0 --column-inserts --clean --if-exists --serializable-deferrable ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" -f "${BACKUP_DATA_DIR}" "${GM_DATABASE}" 2> /dev/null
+pg_dump --verbose --format=directory --compress=0 --column-inserts --clean --if-exists --serializable-deferrable ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --file="${BACKUP_DATA_DIR}" "${GM_DATABASE}" 2> /dev/null
 RESULT="$?"
 if test "${RESULT}" != "0" ; then
 	echo "Cannot dump database content into [${BACKUP_DATA_DIR}] (${RESULT}). Aborting."
@@ -153,10 +153,7 @@ if test "${RESULT}" != "0" ; then
 fi
 # roles
 ROLES_FILE="${BACKUP_BASENAME}-roles.sql"
-# -r -> -g for older versions
-#ROLES=`psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --command="select gm.get_users('${GM_DATABASE}');"`
-ROLES=$(psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --command="select gm.get_users('${GM_DATABASE}');")
-#"
+ROLES=$(psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --command="select gm.get_users('${GM_DATABASE}');")	#"
 RESULT="$?"
 if test "${RESULT}" != "0" ; then
 	echo "Cannot list database roles (${RESULT}). Aborting."
@@ -181,6 +178,7 @@ fi
 	echo "-- Below is the result of 'pg_dumpall --roles-only':"
 	echo "-- -----------------------------------------------------"
 } > "${ROLES_FILE}" 2> /dev/null
+# -r -> -g for older versions
 sudo --user=postgres pg_dumpall --verbose --roles-only ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username=postgres >> "${ROLES_FILE}" 2> /dev/null
 RESULT="$?"
 if test "${RESULT}" != "0" ; then
@@ -214,7 +212,7 @@ fi
 
 # move "untested" tar archive to final directory
 # so the compression script can pick it up if needed
-mv --force "${TAR_UNTESTED}" "${BACKUP_DIR}/"
+mv --force "${TAR_UNTESTED}" "${BACKUP_DIR}"/
 RESULT="$?"
 if test "${RESULT}" != "0" ; then
 	echo "cannot move TAR archive: ${TAR_UNTESTED} => ${BACKUP_DIR}/"


=====================================
server/gm-restore.sh
=====================================
@@ -23,7 +23,7 @@ set -o pipefail
 
 
 BACKUP="$1"
-if test -z ${BACKUP} ; then
+if test -z "${BACKUP}" ; then
 	echo "====================================================="
 	echo "usage: $0 <backup file>"
 	echo ""
@@ -36,9 +36,9 @@ fi
 echo ""
 echo "==> Trying to restore a GNUmed backup ..."
 echo "    backup file: ${BACKUP}"
-if test ! -r ${BACKUP} ; then
+if test ! -r "${BACKUP}" ; then
 	echo "    ERROR: Cannot access backup file. Aborting."
-	echo "   "`ls -al ${BACKUP}`
+	echo "   $(ls -al "${BACKUP}")"
 	exit 1
 fi
 
@@ -50,23 +50,23 @@ if [ -r ${CONF} ] ; then
 	. ${CONF}
 else
 	echo "    ERROR: Cannot read configuration file ${CONF}. Aborting."
-	echo "   "`ls -al ${CONF}`
+	echo "   $(ls -al ${CONF})"
 	exit 1
 fi
 
 
-mkdir -p ${LOG_BASE}
-TS=`date +%Y-%m-%d_%H-%M-%S`
+mkdir -p "${LOG_BASE}"
+TS=$(date +%Y-%m-%d_%H-%M-%S)
 LOG="${LOG_BASE}/restore-${TS}.log"
 echo "    log: ${LOG}"
-echo "restore: ${TS}" &> ${LOG}
-chmod 0666 ${LOG}
+echo "restore: ${TS}" &> "${LOG}"
+chmod 0666 "${LOG}"
 
 
 if [[ "$BACKUP" =~ .*\.bz2 ]] ; then
 	echo ""
 	echo "==> Testing backup file integrity ..."
-	bzip2 -tv $BACKUP &>> ${LOG}
+	bzip2 -tv "$BACKUP" &>> "${LOG}"
 	if test $? -ne 0 ; then
 		echo "    ERROR: Integrity check failed. Aborting."
 		echo ""
@@ -82,21 +82,22 @@ echo ""
 echo "==> Setting up workspace ..."
 WORK_DIR="${WORK_DIR_BASE}/gm-restore_${TS}/"
 echo "    work dir: ${WORK_DIR}"
-mkdir -p -v ${WORK_DIR} &>> ${LOG}
+mkdir -p -v "${WORK_DIR}" &>> "${LOG}"
 if test $? -ne 0 ; then
 	echo "    ERROR: Cannot create workspace. Aborting."
 	echo "           log: ${LOG}"
 	exit 1
 fi
-chmod +rx ${WORK_DIR}
+chmod +rx "${WORK_DIR}"
+ln -s "${LOG}" "${WORK_DIR}"
 
 
 echo ""
 echo "==> Creating copy of backup file ..."
-cp -v ${BACKUP} ${WORK_DIR} &>> ${LOG}
+cp -v "${BACKUP}" "${WORK_DIR}" &>> "${LOG}"
 if test $? -ne 0 ; then
 	echo "    ERROR: Cannot copy backup file. Aborting."
-	echo "   "`ls -al ${BACKUP}`
+	echo "   $(ls -al "${BACKUP}")"
 	echo "   log: ${LOG}"
 	exit 1
 fi
@@ -104,67 +105,64 @@ fi
 
 echo ""
 echo "==> Unpacking backup file ..."
-BACKUP=${WORK_DIR}`basename ${BACKUP}`
+BACKUP=${WORK_DIR}$(basename "${BACKUP}")
 if [[ "${BACKUP}" =~ .*\.bz2 ]] ; then
 	echo " => Decompressing (from bzip2) ..."
-	bunzip2 -v ${BACKUP} &>> ${LOG}
+	bunzip2 -v "${BACKUP}" &>> "${LOG}"
 	if test $? -ne 0 ; then
 		echo "    ERROR: Cannot decompress bzip2 backup file. Aborting."
-		echo "    pwd: "`pwd`
+		echo "    pwd: $(pwd)"
 		echo "    file: ${BACKUP}"
 		echo "    log: ${LOG}"
 		exit 1
 	fi
-	BACKUP=${WORK_DIR}`basename ${BACKUP} .bz2`
+	BACKUP=${WORK_DIR}$(basename "${BACKUP}" .bz2)
 fi
 
 
 echo " => Extracting (from tarball) ..."
-tar -C ${WORK_DIR} -xvf ${BACKUP} &>> ${LOG}
+tar -C "${WORK_DIR}" -xvf "${BACKUP}" &>> "${LOG}"
 if test $? -ne 0 ; then
 	echo "    ERROR: Cannot unpack tarball backup file. Aborting."
-	echo "    pwd: "`pwd`
+	echo "    pwd: $(pwd)"
 	echo "    file: ${BACKUP}"
 	echo "    log: ${LOG}"
 	exit 1
 fi
-BACKUP=${WORK_DIR}`basename ${BACKUP} .tar`
-rm ${BACKUP}.tar &>> ${LOG}
+rm "${BACKUP}" &>> "${LOG}"
+BACKUP="$(ls -1 -d "${WORK_DIR}"*.dir)"
+BACKUP="${WORK_DIR}$(basename "${BACKUP}" .dir)"
+echo "PG backup in dir: ${BACKUP}" &>> "${LOG}"
 
 
 echo ""
 echo "==> Checking target database status ..."
-TARGET_DB=`pg_restore --create --schema-only ${BACKUP}.dir | head -n 40 | grep -i 'create database gnumed_v' | cut -f 3 -d ' '`
+TARGET_DB=$(pg_restore --create --schema-only --file=- "${BACKUP}".dir | head -n 40 | grep -i 'create database gnumed_v' | cut -f 3 -d ' ')
 ERROR="$?"
-echo "${TARGET_DB}" &>> ${LOG}
-#if test ${ERROR} -ne 0 ; then
-#	echo "exit code: ${ERROR}" &>> ${LOG}
-#	echo "    ERROR: Cannot determine target database from backup file. Aborting."
-#	echo "           log: ${LOG}"
-#	exit 1
-#fi
-if test -z ${TARGET_DB} ; then
+echo "${TARGET_DB}" &>> "${LOG}"
+if test -z "${TARGET_DB}" ; then
 	echo "    ERROR: Backup does not create target database ${TARGET_DB}. Aborting."
 	echo "           log: ${LOG}"
 	exit 1
 fi
 echo "    db: ${TARGET_DB}"
-if test `sudo -u postgres psql -l -p ${GM_PORT} | grep ${TARGET_DB} | wc -l` -ne 0 ; then
+if test $(sudo -u postgres psql -l -p "${GM_PORT}" | grep --count "${TARGET_DB}") -ne 0 ; then
 	echo ""
 	echo "    Target database ${TARGET_DB} already exists."
 	echo ""
 	echo "    Restoring will OVERWRITE the existing database."
 	echo "    Are you really positively sure ?"
 	echo ""
-	read -e -p "    [yes / NO]: "
+	read -e -r -p "    [yes / NO]: "
 	if test "${REPLY}" != "yes" ; then
-		echo "${REPLY} (!='yes' => aborted by user)" &>> ${LOG}
+		echo "${REPLY} (!='yes' => aborted by user)" &>> "${LOG}"
 		echo "    Database restore aborted by user."
 		echo "    log: ${LOG}"
 		exit 1
 	fi
-	echo "user requested drop of existing database ${TARGET_DB}" &>> ${LOG}
-	sudo -u postgres dropdb -e -i --if-exists ${TARGET_DB} &>> ${LOG}
+	echo "user requested drop of existing database ${TARGET_DB}" &>> "${LOG}"
+	# shellcheck disable=SC2024
+	sudo -u postgres dropdb -e -i --if-exists "${TARGET_DB}" &>> "${LOG}"
 fi
 
 
@@ -178,21 +176,23 @@ echo "   Remember that in PostgreSQL scripts the comment marker is \"--\"."
 echo ""
 echo "   There are more instructions inside the file."
 echo ""
-read -e -p "   Press <ENTER> to start editing."
-editor ${BACKUP}-roles.sql
+read -e -r -p "   Press <ENTER> to start editing."
+editor "${BACKUP}"-roles.sql
 
 
 echo ""
 echo "==> Setting data file permissions ..."
-chmod -c +r ${BACKUP}-roles.sql &>> ${LOG}
-chown -c postgres ${BACKUP}-roles.sql &>> ${LOG}
-chmod -cR +r ${BACKUP}.dir &>> ${LOG}
-chown -cR postgres ${BACKUP}.dir &>> ${LOG}
+{	chmod -c +r "${BACKUP}-roles.sql" ;
+	chown -c postgres "${BACKUP}-roles.sql" ;
+	chmod -cR +rx "${BACKUP}.dir" ;
+	chown -cR postgres "${BACKUP}.dir" ;
+} &>> "${LOG}"
 
 
 echo ""
 echo "==> Restoring GNUmed roles ..."
-sudo -u postgres psql -e -E -p ${GM_PORT} --single-transaction -f ${BACKUP}-roles.sql &>> ${LOG}
+# shellcheck disable=SC2024
+sudo --user=postgres psql -e -E -p ${GM_PORT} --single-transaction -f "${BACKUP}"-roles.sql &>> "${LOG}"
 if test $? -ne 0 ; then
 	echo "    ERROR: Failed to restore roles. Aborting."
 	echo "           log: ${LOG}"
@@ -211,13 +211,16 @@ echo "==> Restoring GNUmed database ${TARGET_DB} ..."
 # cannot use --single-transaction because CREATE DATABASE does not work inside a transaction
 # need not use --clean because --create already creates "empty" database and does not apply to things copied by createdb :-(
 # need not use --if-exists because --create already creates "empty" database and it does not apply to things copied by createdb :-(
-CMD="sudo -u postgres pg_restore --verbose --create --dbname=template1 --exit-on-error -p ${GM_PORT} ${BACKUP}.dir/"
-echo "${CMD}" &>> ${LOG}
-${CMD} &>> ${LOG}
+#sudo --user=postgres pg_restore --verbose --create --exit-on-error --file=/tmp/gm-restore-test.sql -p ${GM_PORT} ${BACKUP}.dir
+CMD_PG_RESTORE="pg_restore --create --file=- ${BACKUP}.dir"
+CMD_PSQL="psql --echo-errors --dbname=template1 --port=${GM_PORT} --no-psqlrc"
+CMD_SUDO="sudo --user=postgres"
+{
+	${CMD_PG_RESTORE} | grep --ignore-case --invert-match --regexp="alter database ${TARGET_DB} set default_transaction_read_only to.*on" - | ${CMD_SUDO} ${CMD_PSQL}
+} &>> "${LOG}"
 ERROR="$?"
-echo "pg_restore exit code: ${ERROR}" &>> ${LOG}
 if test ${ERROR} -ne 0 ; then
-	echo "    ERROR: failed to restore database. Aborting."
+	echo "    ERROR: failed to restore database (${ERROR}). Aborting."
 	echo "           log: ${LOG}"
 	exit 1
 fi
@@ -225,21 +228,21 @@ fi
 
 echo ""
 echo "==> Analyzing database ${TARGET_DB} ..."
+# shellcheck disable=SC2024
 # --full doesn't make sense since there are no
 # deleted rows in a freshly restored database but
 # we need to update statistics to get decent performance
-sudo -u postgres vacuumdb -v -z -d ${TARGET_DB} -p ${GM_PORT} &>> ${LOG}
-sudo -u postgres vacuumdb -v -Z -d ${TARGET_DB} -p ${GM_PORT} &>> ${LOG}
+sudo --user=postgres vacuumdb -v -Z -d "${TARGET_DB}" --port=${GM_PORT} &>> "${LOG}"
 
 
 # adjusting settings
-gm-adjust_db_settings ${TARGET_DB} &>> ${LOG}
+gm-adjust_db_settings "${TARGET_DB}" &>> "${LOG}"
 
 
 echo ""
 echo "==> Cleaning up ..."
 echo "    log dir: ${LOG_BASE}"
-rm --verbose --dir --recursive --one-file-system ${WORK_DIR} &>> ${LOG}
+rm --verbose --dir --recursive --one-file-system "${WORK_DIR}" &>> "${LOG}"
 
 
 echo ""


=====================================
server/gm-zip+sign_backups.sh
=====================================
@@ -62,7 +62,7 @@ AGGREGATE_EXIT_CODE=0
 
 # find any leftover, untested tar files
 # and test them so they can be compressed
-for TAR_UNTESTED in ${BACKUP_BASENAME}-*.tar.untested ; do
+for TAR_UNTESTED in "${BACKUP_BASENAME}"-*.tar.untested ; do
 
 	# test
 	tar --extract --to-stdout --file="${TAR_UNTESTED}" > /dev/null
@@ -89,7 +89,7 @@ done
 
 
 # zip up any backups
-for TAR_FINAL in ${BACKUP_BASENAME}-*.tar ; do
+for TAR_FINAL in "${BACKUP_BASENAME}"-*.tar ; do
 
 	BZ2_FINAL="${TAR_FINAL}.bz2"
 	BZ2_SCRATCH="${BZ2_FINAL}.partial"


=====================================
server/pycommon/gmBackendListener.py
=====================================
@@ -219,7 +219,7 @@ class gmBackendListener(gmBorg.cBorg):
 				self.__notifications_received += 1
 				if self.debug:
 					print(notification)
-				_log.debug('#%s: %s (first param is PID of sending backend)', self.__notifications_received, notification)
+				_log.debug('#%s: %s (first param: PID of sending backend; this backend: %s)', self.__notifications_received, notification, self.backend_pid)
 				# decode payload
 				payload = notification.payload.split('::')
 				operation = None


=====================================
server/pycommon/gmConnectionPool.py
=====================================
@@ -649,6 +649,14 @@ def exception_is_connection_loss(exc):
 		# not a PG exception
 		return False
 
+	try:
+		if isinstance(exc, dbapi.errors.AdminShutdown):
+			_log.debug('indicates connection loss due to admin shutdown')
+			return True
+
+	except AttributeError:	# psycopg2 2.7/2.8 transition
+		pass
+
 	try:
 		msg = '%s' % exc.args[0]
 	except (AttributeError, IndexError, TypeError):
@@ -667,8 +675,8 @@ def exception_is_connection_loss(exc):
 			('abnorm' in msg)
 				or
 			('end' in msg)
-#				or
-#			('oute' in msg)
+				or
+			('no route' in msg)
 		)
 	) or (
 		# InterfaceError


=====================================
server/pycommon/gmDateTime.py
=====================================
@@ -647,10 +647,11 @@ def format_interval_medically(interval=None):
 	This isn't mathematically correct but close enough for display.
 	"""
 	# more than 1 year ?
-	if interval.days > 363:
-		years, days = divmod(interval.days, 364)
+	if interval.days > 364:
+		years, days = divmod(interval.days, avg_days_per_gregorian_year)
 		leap_days, tmp = divmod(years, 4)
-		months, day = divmod((days + leap_days), 30.33)
+		days_left_without_leap_days = days - leap_days
+		months, day = divmod((days_left_without_leap_days), 30.33)
 		if int(months) == 0:
 			return "%s%s" % (int(years), _('interval_format_tag::years::y')[-1:])
 		return "%s%s %s%s" % (int(years), _('interval_format_tag::years::y')[-1:], int(months), _('interval_format_tag::months::m')[-1:])
@@ -1536,22 +1537,6 @@ def str2pydt_matches(str2parse=None, patterns=None):
 	matches.extend(__single_char2py_dt(str2parse))
 	matches.extend(__explicit_offset2py_dt(str2parse))
 
-	# no more with Python3
-#	# try mxDT parsers
-#	try:
-#		date = mxDT.Parser.DateFromString (
-#			text = str2parse,
-#			formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
-#		)
-#		matches.append ({
-#			'data': mxdt2py_dt(date),
-#			'label': date.strftime('%Y-%m-%d')
-#		})
-#	except (ValueError, OverflowError):
-#		pass
-#	except mxDT.RangeError:
-#		pass
-
 	# apply explicit patterns
 	if patterns is None:
 		patterns = []
@@ -1574,12 +1559,27 @@ def str2pydt_matches(str2parse=None, patterns=None):
 
 	patterns.append('%Y.%m.%d')
 
+	parts = str2parse.split(maxsplit = 1)
+	hour = 11
+	minute = 11
+	second = 11
+	if len(parts) > 1:
+		for pattern in ['%H:%M', '%H:%M:%S']:
+			try:
+				date = pyDT.datetime.strptime(parts[1], pattern)
+				hour = date.hour
+				minute = date.minute
+				second = date.second
+				break
+			except ValueError:
+				# C-level overflow
+				continue
 	for pattern in patterns:
 		try:
-			date = pyDT.datetime.strptime(str2parse, pattern).replace (
-				hour = 11,
-				minute = 11,
-				second = 11,
+			date = pyDT.datetime.strptime(parts[0], pattern).replace (
+				hour = hour,
+				minute = minute,
+				second = second,
 				tzinfo = gmCurrentLocalTimezone
 			)
 			matches.append ({
@@ -1590,6 +1590,7 @@ def str2pydt_matches(str2parse=None, patterns=None):
 			# C-level overflow
 			continue
 
+
 	return matches
 
 #===========================================================================
@@ -1943,65 +1944,50 @@ def str2fuzzy_timestamp_matches(str2parse=None, default_time=None, patterns=None
 		} for m in __explicit_offset2py_dt(str2parse)
 	])
 
-	# reactivate, once mxDT becomes available on Py3k
-#	# try mxDT parsers
-#	try:
-#		# date ?
-#		date_only = mxDT.Parser.DateFromString (
-#			text = str2parse,
-#			formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
-#		)
-#		# time, too ?
-#		time_part = mxDT.Parser.TimeFromString(text = str2parse)
-#		datetime = date_only + time_part
-#		if datetime == date_only:
-#			accuracy = acc_days
-#			if isinstance(default_time, mxDT.DateTimeDeltaType):
-#				datetime = date_only + default_time
-#				accuracy = acc_minutes
-#		else:
-#			accuracy = acc_subseconds
-#		fts = cFuzzyTimestamp (
-#			timestamp = datetime,
-#			accuracy = accuracy
-#		)
-#		matches.append ({
-#			'data': fts,
-#			'label': fts.format_accurately()
-#		})
-#	except ValueError:
-#		pass
-#	except mxDT.RangeError:
-#		pass
-
 	if patterns is None:
 		patterns = []
 	patterns.extend([
-		['%Y-%m-%d', acc_days],
-		['%y-%m-%d', acc_days],
-		['%Y/%m/%d', acc_days],
-		['%y/%m/%d', acc_days],
-
-		['%d-%m-%Y', acc_days],
-		['%d-%m-%y', acc_days],
-		['%d/%m/%Y', acc_days],
-		['%d/%m/%y', acc_days],
-		['%d.%m.%Y', acc_days],
-
-		['%m-%d-%Y', acc_days],
-		['%m-%d-%y', acc_days],
-		['%m/%d/%Y', acc_days],
-		['%m/%d/%y', acc_days]
+		'%Y-%m-%d',
+		'%y-%m-%d',
+		'%Y/%m/%d',
+		'%y/%m/%d',
+		'%d-%m-%Y',
+		'%d-%m-%y',
+		'%d/%m/%Y',
+		'%d/%m/%y',
+		'%d.%m.%Y',
+		'%m-%d-%Y',
+		'%m-%d-%y',
+		'%m/%d/%Y',
+		'%m/%d/%y'
 	])
+
+	parts = str2parse.split(maxsplit = 1)
+	hour = 11
+	minute = 11
+	second = 11
+	acc = acc_days
+	if len(parts) > 1:
+		for pattern in ['%H:%M', '%H:%M:%S']:
+			try:
+				date = pyDT.datetime.strptime(parts[1], pattern)
+				hour = date.hour
+				minute = date.minute
+				second = date.second
+				acc = acc_minutes
+				break
+			except ValueError:
+				# C-level overflow
+				continue
 	for pattern in patterns:
 		try:
-			ts = pyDT.datetime.strptime(str2parse, pattern[0]).replace (
-				hour = 11,
-				minute = 11,
-				second = 11,
+			ts = pyDT.datetime.strptime(parts[0], pattern).replace (
+				hour = hour,
+				minute = minute,
+				second = second,
 				tzinfo = gmCurrentLocalTimezone
 			)
-			fts = cFuzzyTimestamp(timestamp = ts, accuracy = pattern[1])
+			fts = cFuzzyTimestamp(timestamp = ts, accuracy = acc)
 			matches.append ({
 				'data': fts,
 				'label': fts.format_accurately()
@@ -2009,7 +1995,6 @@ def str2fuzzy_timestamp_matches(str2parse=None, default_time=None, patterns=None
 		except ValueError:
 			# C-level overflow
 			continue
-
 	return matches
 
 #===========================================================================
@@ -2234,10 +2219,9 @@ if __name__ == '__main__':
 			pyDT.timedelta(days = 366),
 			pyDT.timedelta(days = 367),
 			pyDT.timedelta(days = 400),
-			pyDT.timedelta(weeks = 52 * 30),
-			pyDT.timedelta(weeks = 52 * 79, days = 33)
+			pyDT.timedelta(weeks = 53 * 30),
+			pyDT.timedelta(weeks = 53 * 79, days = 33)
 		]
-
 		idx = 1
 		for intv in intervals:
 			print ('%s) %s -> %s' % (idx, intv, format_interval_medically(intv)))
@@ -2424,7 +2408,7 @@ if __name__ == '__main__':
 	init()
 
 	#test_date_time()
-	#test_str2fuzzy_timestamp_matches()
+	test_str2fuzzy_timestamp_matches()
 	#test_get_date_of_weekday_in_week_of_date()
 	#test_cFuzzyTimeStamp()
 	#test_get_pydt()
@@ -2434,6 +2418,6 @@ if __name__ == '__main__':
 	#test_str2pydt()
 	#test_pydt_strftime()
 	#test_calculate_apparent_age()
-	test_is_leap_year()
+	#test_is_leap_year()
 
 #===========================================================================


=====================================
server/pycommon/gmI18N.py
=====================================
@@ -237,10 +237,11 @@ def activate_locale():
 		_log.exception('Windows does not support locale.LC_ALL')
 	except Exception:
 		_log.exception('error activating user-default locale')
+		_log.log_stack_trace()
 	__log_locale_settings('locale settings after activating user-default locale')
 	# assume en_EN if we did not find any locale settings
 	if loc in [None, 'C']:
-		_log.error('the current system locale is still [None] or [C], assuming [en_EN]')
+		_log.error('the current system locale is still [None] or [C], falling back to [en_EN]')
 		system_locale = "en_EN"
 	else:
 		system_locale = loc


=====================================
server/pycommon/gmNetworkTools.py
=====================================
@@ -326,7 +326,7 @@ def check_for_update(url=None, current_branch=None, current_version=None, consid
 	)
 
 	msg += '\n\n'
-	msg += _('Details are found on <http://wiki.gnumed.de>.\n')
+	msg += _('Details are found on <http://www.gnumed.de>.\n')
 	msg += '\n'
 	msg += _('Version information loaded from:\n\n %s') % url
 


=====================================
server/pycommon/gmPG2.py
=====================================
@@ -916,7 +916,7 @@ def lock_row(link_obj=None, table=None, pk=None, exclusive=False):
 		cmd = """SELECT pg_try_advisory_lock('%s'::regclass::oid::int, %s)""" % (table, pk)
 	else:
 		cmd = """SELECT pg_try_advisory_lock_shared('%s'::regclass::oid::int, %s)""" % (table, pk)
-	rows, idx = run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd}], get_col_idx = False, return_data = True)
+	rows, idx = run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd}], get_col_idx = False)
 	if rows[0][0]:
 		return True
 
@@ -934,7 +934,7 @@ def unlock_row(link_obj=None, table=None, pk=None, exclusive=False):
 		cmd = "SELECT pg_advisory_unlock('%s'::regclass::oid::int, %s)" % (table, pk)
 	else:
 		cmd = "SELECT pg_advisory_unlock_shared('%s'::regclass::oid::int, %s)" % (table, pk)
-	rows, idx = run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd}], get_col_idx = False, return_data = True)
+	rows, idx = run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd}], get_col_idx = False)
 	if rows[0][0]:
 		return True
 
@@ -943,7 +943,7 @@ def unlock_row(link_obj=None, table=None, pk=None, exclusive=False):
 
 #------------------------------------------------------------------------
 def row_is_locked(table=None, pk=None):
-	"""Looks at pk_locks
+	"""Looks at pg_locks.
 
 	- does not take into account locks other than 'advisory', however
 	"""
@@ -959,6 +959,7 @@ def row_is_locked(table=None, pk=None):
 	if rows[0][0]:
 		_log.debug('row is locked: [%s] [%s]', table, pk)
 		return True
+
 	_log.debug('row is NOT locked: [%s] [%s]', table, pk)
 	return False
 
@@ -2505,7 +2506,7 @@ SELECT to_timestamp (foofoo,'YYMMDD.HH24MI') FROM (
 
 		row_is_locked(table = 'dem.identity', pk = 12)
 
-		print("1st connection:")
+		print("on 1st connection:")
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
 		print(" 1st shared lock succeeded:", lock_row(table = 'dem.identity', pk = 12, exclusive = False))
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
@@ -2517,20 +2518,20 @@ SELECT to_timestamp (foofoo,'YYMMDD.HH24MI') FROM (
 		print("   `-> unlock succeeded:", unlock_row(table = 'dem.identity', pk = 12, exclusive = True))
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
 
-		print("2nd connection:")
+		print("on 2nd connection:")
 		conn = get_raw_connection(readonly=True)
 		print(" shared lock should succeed:", lock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = False))
 		print(" `-> unlock succeeded:", unlock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = False))
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
-		print(" exclusive lock succeeded ?", lock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = True), "(should fail)")
+		print(" exclusive lock succeeded:", lock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = True), "(should be False)")
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
 
-		print("1st connection:")
+		print("on 1st connection again:")
 		print(" unlock succeeded:", unlock_row(table = 'dem.identity', pk = 12, exclusive = False))
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
 
-		print("2nd connection:")
-		print(" exclusive lock should succeed", lock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = True))
+		print("on 2nd connection:")
+		print(" exclusive lock should succeed:", lock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = True))
 		print(" locked:", row_is_locked(table = 'dem.identity', pk = 12))
 		print("  shared lock should succeed:", lock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = False))
 		print("  `-> unlock succeeded:", unlock_row(link_obj = conn, table = 'dem.identity', pk = 12, exclusive = False))
@@ -2619,9 +2620,9 @@ SELECT to_timestamp (foofoo,'YYMMDD.HH24MI') FROM (
 	#test_run_query()
 	#test_schema_exists()
 	#test_get_foreign_key_names()
-	#test_row_locks()
+	test_row_locks()
 	#test_faulty_SQL()
 	#test_log_settings()
-	test_get_db_fingerprint()
+	#test_get_db_fingerprint()
 
 # ======================================================================


=====================================
server/pycommon/gmTools.py
=====================================
@@ -1310,38 +1310,48 @@ def xml_escape_string(text=None):
 	return xml_tools.escape(text)
 
 #---------------------------------------------------------------------------
-def tex_escape_string(text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False):
+def tex_escape_string(text:str=None, replace_known_unicode:bool=True, replace_eol:bool=False, keep_visual_eol:bool=False) -> str:
 	"""Check for special TeX characters and transform them.
 
-		replace_eol:
-			replaces "\n" with "\\newline"
-		keep_visual_eol:
-			replaces "\n" with "\\newline \n" such that
+	Args:
+		text: plain (unicode) text to escape for LaTeX processing,
+			note that any valid LaTeX code contained within will be
+			escaped, too
+		replace_eol: replaces "\n" with "\\newline{}"
+		keep_visual_eol: replaces "\n" with "\\newline{}%\n" such that
 			both LaTeX will know to place a line break
 			at this point as well as the visual formatting
 			is preserved in the LaTeX source (think multi-
 			row table cells)
+
+	Returns:
 	"""
-	text = text.replace('\\', '\\textbackslash')			# requires \usepackage{textcomp} in LaTeX source
-	text = text.replace('^', '\\textasciicircum')
-	text = text.replace('~', '\\textasciitilde')
-
-	text = text.replace('{', '\\{')
-	text = text.replace('}', '\\}')
-	text = text.replace('%', '\\%')
-	text = text.replace('&', '\\&')
-	text = text.replace('#', '\\#')
-	text = text.replace('$', '\\$')
-	text = text.replace('_', '\\_')
+	# must happen first
+	text = text.replace('{', '-----{{{{{-----')
+	text = text.replace('}', '-----}}}}}-----')
+
+	text = text.replace('\\', '\\textbackslash{}')			# requires \usepackage{textcomp} in LaTeX source
+
+	text = text.replace('-----{{{{{-----', '\\{{}')
+	text = text.replace('-----}}}}}-----', '\\}{}')
+
+	text = text.replace('^', '\\textasciicircum{}')
+	text = text.replace('~', '\\textasciitilde{}')
+
+	text = text.replace('%', '\\%{}')
+	text = text.replace('&', '\\&{}')
+	text = text.replace('#', '\\#{}')
+	text = text.replace('$', '\\${}')
+	text = text.replace('_', '\\_{}')
 	if replace_eol:
 		if keep_visual_eol:
-			text = text.replace('\n', '\\newline \n')
+			text = text.replace('\n', '\\newline{}%\n')
 		else:
-			text = text.replace('\n', '\\newline ')
+			text = text.replace('\n', '\\newline{}')
 
 	if replace_known_unicode:
 		# this should NOT be replaced for Xe(La)Tex
-		text = text.replace(u_euro, '\\EUR')		# requires \usepackage{textcomp} in LaTeX source
+		text = text.replace(u_euro, '\\euro{}')		# requires \usepackage[official]{eurosym} in LaTeX source
 		text = text.replace(u_sum, '$\\Sigma$')
 
 	return text
@@ -2467,7 +2477,7 @@ second line\n
 	#test_xml_escape()
 	#test_strip_trailing_empty_lines()
 	#test_fname_stem()
-	#test_tex_escape()
+	test_tex_escape()
 	#test_rst2latex_snippet()
 	#test_dir_is_empty()
 	#test_compare_dicts()
@@ -2482,7 +2492,7 @@ second line\n
 	#test_enumerate_optical_writers()
 	#test_copy_tree_content()
 	#test_mk_sandbox_dir()
-	test_make_table_from_dicts()
+	#test_make_table_from_dicts()
 	#test_decorate_window_title()
 
 #===========================================================================


=====================================
server/sql/v21-v22/dynamic/v22-clin-remove_old_empty_encounters.sql
=====================================
@@ -46,6 +46,9 @@ BEGIN
 	DELETE FROM clin.encounter WHERE
 		clin.encounter.fk_patient = _pk_identity
 			AND
+		-- is this the active encounter somewhere ?
+		(SELECT pg_try_advisory_lock(''clin.encounter''::regclass::oid::int, clin.encounter.pk))
+			AND
 		age(clin.encounter.last_affirmed) > _usable_minimum_encounter_age
 			AND
 		NOT EXISTS (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
@@ -65,11 +68,11 @@ BEGIN
 		NOT EXISTS (SELECT 1 FROM clin.suppressed_hint WHERE fk_encounter = clin.encounter.pk)
 	;
 
-	return true;
+	RETURN TRUE;
 END;';
 
 comment on function clin.remove_old_empty_encounters(integer, interval) is
 	'Remove empty encounters older than a definable minimum age from a patient.';
 
 -- --------------------------------------------------------------
-select gm.log_script_insertion('v22-clin-remove_old_empty_encounters.sql', '22.0');
+select gm.log_script_insertion('v22-clin-remove_old_empty_encounters.sql', '22.16');


=====================================
server/sql/v21-v22/fixups/v22-clin-remove_old_empty_encounters-fixup.sql
=====================================
@@ -0,0 +1,78 @@
+-- ==============================================================
+-- GNUmed database schema change script
+--
+-- License: GPL v2 or later
+-- Author: karsten.hilbert at gmx.net
+--
+-- ==============================================================
+-- force terminate + exit(3) on errors if non-interactive
+\set ON_ERROR_STOP 1
+
+--set default_transaction_read_only to off;
+set check_function_bodies to on;
+
+-- --------------------------------------------------------------
+create or replace function clin.remove_old_empty_encounters(integer, interval)
+	returns boolean
+	language 'plpgsql'
+	security definer
+	as '
+DECLARE
+	_pk_identity alias for $1;
+	_defined_minimum_encounter_age alias for $2;
+	_usable_minimum_encounter_age interval;
+	_no_of_encounters integer;
+BEGIN
+	-- does person exist ?
+	perform 1 from dem.identity where pk = _pk_identity;
+	if not found then
+		raise exception ''clin.remove_old_empty_encounters(person=%, min_age=%): person [%] does not exist'', _pk_identity, _defined_minimum_encounter_age, _pk_identity;
+		return false;
+	end if;
+
+	SELECT count(1) INTO STRICT _no_of_encounters
+	FROM clin.encounter	WHERE fk_patient = _pk_identity;
+	IF _no_of_encounters < 2 THEN
+		raise notice ''clin.remove_old_empty_encounters(person=%, min_age=%): there are less than 2 encounters for this patient'', _pk_identity, _defined_minimum_encounter_age;
+		return false;
+	END IF;
+
+	if _defined_minimum_encounter_age < ''3 days''::interval then
+		_usable_minimum_encounter_age := ''3 days''::interval;
+	else
+		_usable_minimum_encounter_age := _defined_minimum_encounter_age;
+	end if;
+
+	DELETE FROM clin.encounter WHERE
+		clin.encounter.fk_patient = _pk_identity
+			AND
+		-- is this the active encounter somewhere ?
+		(SELECT pg_try_advisory_lock(''clin.encounter''::regclass::oid::int, clin.encounter.pk))
+			AND
+		age(clin.encounter.last_affirmed) > _usable_minimum_encounter_age
+			AND
+		NOT EXISTS (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM blobs.doc_med WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM clin.episode WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM clin.health_issue WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM clin.allergy_state WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM bill.bill_item WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM clin.external_care WHERE fk_encounter = clin.encounter.pk)
+			AND
+		NOT EXISTS (SELECT 1 FROM clin.suppressed_hint WHERE fk_encounter = clin.encounter.pk)
+	;
+
+	RETURN TRUE;
+END;';
+
+comment on function clin.remove_old_empty_encounters(integer, interval) is
+	'Remove empty encounters older than a definable minimum age from a patient.';
+
+-- --------------------------------------------------------------
+select gm.log_script_insertion('v22-clin-remove_old_empty_encounters-fixup.sql', '22.16');


=====================================
server/sql/v21-v22/fixups/v22-release_notes-fixup.sql
=====================================
@@ -17,22 +17,38 @@ INSERT INTO dem.message_inbox (
 ) VALUES (
 	(select pk from dem.staff where db_user = 'any-doc'),
 	(select pk_type from dem.v_inbox_item_type where type = 'memo' and category = 'administrative'),
-	'Release Notes for GNUmed 1.8.5 (database v22.15)',
-	'GNUmed 1.8.5 Release Notes:
+	'Release Notes for GNUmed 1.8.6 (database v22.16)',
+	'GNUmed 1.8.6 Release Notes:
 
-	1.8.5
+	1.8.6
 
-FIX: image conversion w/o target extension [thanks ...]
-FIX: SVG icon [thanks freddii]
-FIX: lab: tab "most-recent": [x]ing "show missing" throws exception
-FIX: log: exception on inaccessible attributes
+FIX: unlocking encounters (missing import)
+FIX: row locking code
+FIX: metadata: screenshots URL in appdata.xml
+FIX: GUI: screenshot function [thanks The-o]
+FIX: medical interval formatting [thanks brulefa]
+FIX: date/time input: time parsing
+FIX: date/time input: function key to weekday mapping
+FIX: encounters: do not crash if current encounter is deleted
+FIX: paperwork: fix emr_journal placeholder LaTeX escaping [thanks Marc]
 
-NEW: add CoVid-2019 vaccines
+IMPROVED: desktop entry file [thanks freddii]
+IMPROVED: patient switch: encounter checking
+IMPROVED: pat overview: problems display
+IMPROVED: DB: connection loss detection
+IMPROVED: measurement EA: previous result formatting
+IMPROVED: DB: login problem logging
+IMPROVED: top pane: show age of results in tooltip
+IMPROVED: paperwork: LaTeX formatting of text
+IMPROVED: EMR: extend search to select demographics [thanks brulefa]
+IMPROVED: startup: bash code [thanks shellcheck]
 
-	22.15
+	22.16
 
-FIX: message inbox view
+IMPROVED: bootstrapper: check disk space
+
+FIX: check encounter lock in clin.remove_old_empty_encounters()
 ');
 
 -- --------------------------------------------------------------
-select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.15 at 1.8.5');
+select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.16 at 1.8.6');



View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/compare/e2d537e4a1e8c2895099b6ae8c861fa07b75a488...d23cc38f0d3802d9b4f1f70d304027e048bea7a7

-- 
View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/compare/e2d537e4a1e8c2895099b6ae8c861fa07b75a488...d23cc38f0d3802d9b4f1f70d304027e048bea7a7
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20210829/7917d407/attachment-0001.htm>


More information about the debian-med-commit mailing list