[med-svn] [Git][med-team/gnumed-server][master] 6 commits: New upstream version 22.12

Andreas Tille gitlab at salsa.debian.org
Wed Mar 25 14:18:05 GMT 2020



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


Commits:
d6ac831e by Andreas Tille at 2020-03-25T15:09:08+01:00
New upstream version 22.12
- - - - -
d1686708 by Andreas Tille at 2020-03-25T15:09:08+01:00
routine-update: New upstream version

- - - - -
e7856cd2 by Andreas Tille at 2020-03-25T15:09:46+01:00
Update upstream source from tag 'upstream/22.12'

Update to upstream version '22.12'
with Debian dir 063d73d998e56efea6e46f0d028c4f9ab96a1783
- - - - -
bd7aa982 by Andreas Tille at 2020-03-25T15:11:59+01:00
postgresql-plr is a virtual package - it needs a real package that provides it in Suggests

- - - - -
b59d99f1 by Andreas Tille at 2020-03-25T15:13:35+01:00
Adapt patch

- - - - -
f3240512 by Andreas Tille at 2020-03-25T15:14:42+01:00
Upload to unstable

- - - - -


15 changed files:

- debian/changelog
- debian/control
- debian/patches/python3.patch
- server/bootstrap/bootstrap_gm_db_system.py
- server/doc/schema/gnumed-entire_schema.html
- server/gm-backup.sh
- server/gm-backup_data.sh
- server/gm-fingerprint_db.py
- server/pycommon/gmBusinessDBObject.py
- server/pycommon/gmDateTime.py
- server/pycommon/gmMimeMagic.py
- server/pycommon/gmPG2.py
- server/pycommon/gmShellAPI.py
- server/pycommon/gmTools.py
- server/sql/v21-v22/fixups/v22-release_notes-fixup.sql


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+gnumed-server (22.12-1) unstable; urgency=medium
+
+  * New upstream version
+  * postgresql-plr is a virtual package - it needs a real package
+    that provides it in Suggests
+
+ -- Andreas Tille <tille at debian.org>  Wed, 25 Mar 2020 15:13:58 +0100
+
 gnumed-server (22.11-1) unstable; urgency=medium
 
   * Rename debian/NEWS.Debian to debian/NEWS


=====================================
debian/control
=====================================
@@ -30,7 +30,7 @@ Depends: ${python3:Depends},
          python3-psycopg2
 Recommends: barman
 Suggests: postgresql-contrib,
-          postgresql-plr,
+          postgresql-12-plr | postgresql-plr,
           bacula-console,
           postgresql-filedump,
           orthanc-postgresql | orthanc,


=====================================
debian/patches/python3.patch
=====================================
@@ -2,25 +2,8 @@ Description: Fix some remaining python calls to python3
 Author: Andreas Tille <tille at debian.org>
 Last-Update: Tue, 03 Mar 2020 15:17:28 +0100
 
---- a/server/gm-fingerprint_db.py
-+++ b/server/gm-fingerprint_db.py
-@@ -1,4 +1,4 @@
--#!/usr/bin/env python
-+#!/usr/bin/env python3
- 
- #==============================================================
- # This script can be used to fingerprint a GNUmed database.
 --- a/server/pycommon/gmMimeMagic.py
 +++ b/server/pycommon/gmMimeMagic.py
-@@ -5,7 +5,7 @@
-  (C)opyright 2000 Jason Petrone <jp at demonseed.net>
-  All Rights Reserved
- 
-- Command Line Usage: running as `python magic.py file` will print
-+ Command Line Usage: running as `python3 magic.py file` will print
- 										 a description of what 'file' is.
- 
-  Module Usage:
 @@ -225,20 +225,20 @@ magic = [
  	[0L, 'string', '=', '#! /usr/local/bin/perl', 'application/x-perl'],
  	[0L, 'string', '=', '#! /usr/local/bin/perl', 'application/x-perl'],
@@ -56,32 +39,3 @@ Last-Update: Tue, 03 Mar 2020 15:17:28 +0100
  	[0L, 'string', '=', '#!/bin/rc', 'text/script'],
  	[0L, 'string', '=', '#! /bin/rc', 'text/script'],
  	[0L, 'string', '=', '#! /bin/rc', 'text/script'],
---- a/server/bootstrap/bootstrap_gm_db_system.py
-+++ b/server/bootstrap/bootstrap_gm_db_system.py
-@@ -1,6 +1,4 @@
- #!/usr/bin/python3
--##!/usr/bin/env python
--##!/usr/bin/python2.7-dbg
- 
- __doc__="""GNUmed schema installation.
- 
---- a/server/gm-backup_data.sh
-+++ b/server/gm-backup_data.sh
-@@ -18,7 +18,7 @@ exit 1
- #
- # To restore the data-only dump do this:
- #
--# 1) $> python gmDBPruningDMLGenerator.py <data only dump>
-+# 1) $> python3 gmDBPruningDMLGenerator.py <data only dump>
- # 2) $> psql -d gnumed_vX -U gm-dbo -f <data only dump>-prune_tables.sql
- # 3) $> psql -d gnumed_vX -U gm-dbo -f <data only dump>
- #
-@@ -28,7 +28,7 @@ exit 1
- # To speed things up you can replace step 1) with:
- #
- # 1a) $> cut -f -5 -d " " <data only dump> | grep -E "^(SET)|(INSERT)" > tmp.sql
--# 1b) $> python gmDBPruningDMLGenerator.py tmp.sql
-+# 1b) $> python3 gmDBPruningDMLGenerator.py tmp.sql
- #
- # and use that in step 2):
- #


=====================================
server/bootstrap/bootstrap_gm_db_system.py
=====================================
@@ -1,6 +1,4 @@
 #!/usr/bin/python3
-##!/usr/bin/env python
-##!/usr/bin/python2.7-dbg
 
 __doc__="""GNUmed schema installation.
 


=====================================
server/doc/schema/gnumed-entire_schema.html
=====================================
@@ -112,7 +112,7 @@
   <body>
 
     <!-- Primary Index -->
-	<p><br><br>Dumped on 2020-02-29</p>
+	<p><br><br>Dumped on 2020-03-25</p>
 <h1><a name="index">Index of database - gnumed_v22</a></h1>
 <ul>
     


=====================================
server/gm-backup.sh
=====================================
@@ -100,7 +100,11 @@ fi
 OUR_VER=$(echo "${GM_DATABASE}" | cut -f 2 -d v)
 SQL_COMPARE_VERSIONS="SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname LIKE 'gnumed_v%' AND substring(datname from 9 for 3)::integer > '${OUR_VER}');"
 HAS_HIGHER_VER=$(sudo --user=postgres psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --command="${SQL_COMPARE_VERSIONS}")
-#HAS_HIGHER_VER=`sudo --user=postgres psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --command="${SQL_COMPARE_VERSIONS}"`
+RESULT="$?"
+if test "${RESULT}" != "0" ; then
+	echo "Cannot sanity check database version (${RESULT}). Aborting."
+	exit ${RESULT}
+fi
 if test "${HAS_HIGHER_VER}" = "t" ; then
 	echo "Backing up database ${GM_DATABASE}."
 	echo ""
@@ -142,10 +146,22 @@ echo "backup: ${TS}" > ${TS_FILE}
 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
+RESULT="$?"
+if test "${RESULT}" != "0" ; then
+	echo "Cannot dump database content into [${BACKUP_DATA_DIR}] (${RESULT}). Aborting."
+	exit ${RESULT}
+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."
+	exit ${RESULT}
+fi
 {
 	echo "-- -----------------------------------------------------"
 	echo "-- Below find a list of database roles which were in use"
@@ -166,6 +182,11 @@ ROLES=`psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG
 	echo "-- -----------------------------------------------------"
 } > "${ROLES_FILE}" 2> /dev/null
 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
+	echo "Cannot dump database roles into [${ROLES_FILE}] (${RESULT}). Aborting."
+	exit ${RESULT}
+fi
 
 
 # create tar archive


=====================================
server/gm-backup_data.sh
=====================================
@@ -18,7 +18,7 @@ exit 1
 #
 # To restore the data-only dump do this:
 #
-# 1) $> python gmDBPruningDMLGenerator.py <data only dump>
+# 1) $> python3 gmDBPruningDMLGenerator.py <data only dump>
 # 2) $> psql -d gnumed_vX -U gm-dbo -f <data only dump>-prune_tables.sql
 # 3) $> psql -d gnumed_vX -U gm-dbo -f <data only dump>
 #
@@ -28,7 +28,7 @@ exit 1
 # To speed things up you can replace step 1) with:
 #
 # 1a) $> cut -f -5 -d " " <data only dump> | grep -E "^(SET)|(INSERT)" > tmp.sql
-# 1b) $> python gmDBPruningDMLGenerator.py tmp.sql
+# 1b) $> python3 gmDBPruningDMLGenerator.py tmp.sql
 #
 # and use that in step 2):
 #


=====================================
server/gm-fingerprint_db.py
=====================================
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #==============================================================
 # This script can be used to fingerprint a GNUmed database.


=====================================
server/pycommon/gmBusinessDBObject.py
=====================================
@@ -22,7 +22,7 @@ One way is to pass a "primary key equivalent" object into
 __init__(). Refetch_payload() will then pull the data from
 the backend. Another way would be to fetch the data outside
 the instance and pass it in via the <row> argument. In that
-case the instance will not initially connect to the databse
+case the instance will not initially connect to the database
 which may offer a great boost to performance.
 
 Values API
@@ -326,7 +326,7 @@ def manage_xxx()
 		self._ext_cache = {}	# the cache for extended method's results
 		self._is_modified = False
 
-		# check descendants
+		# sanity check child implementions
 		self.__class__._cmd_fetch_payload
 		self.__class__._cmds_store_payload
 		self.__class__._updatable_fields
@@ -347,7 +347,7 @@ def manage_xxx()
 			  * the primary key WHERE condition must be
 				a simple column
 			- a dictionary of values
-			  * the primary key where condition must be a
+			  * the primary key WHERE condition must be a
 				subselect consuming the dict and producing
 				the single-value primary key
 		"""
@@ -448,6 +448,7 @@ def manage_xxx()
 
 		self._ext_cache[attribute] = getter()
 		return self._ext_cache[attribute]
+
 	#--------------------------------------------------------
 	def __setitem__(self, attribute, value):
 
@@ -676,8 +677,10 @@ def manage_xxx()
 		if len(rows) == 0:
 			_log.error('[%s:%s]: no such instance' % (self.__class__.__name__, self.pk_obj))
 			return False
+
 		if len(rows) > 1:
 			raise AssertionError('[%s:%s]: %s instances !' % (self.__class__.__name__, self.pk_obj, len(rows)))
+
 		self._payload = rows[0]
 		return True
 


=====================================
server/pycommon/gmDateTime.py
=====================================
@@ -373,38 +373,10 @@ def pydt_strftime(dt=None, format='%Y %b %d  %H:%M.%S', accuracy=None, none_str=
 
 	try:
 		return dt.strftime(format)
+
 	except ValueError:
 		_log.exception()
 		return 'strftime() error'
-		#_log.exception('Python cannot strftime() this <datetime>, trying ourselves')
-
-#	if isinstance(dt, pyDT.date):
-#		accuracy = acc_days
-#
-#	if accuracy == acc_days:
-#		return '%04d-%02d-%02d' % (
-#			dt.year,
-#			dt.month,
-#			dt.day
-#		)
-#
-#	if accuracy == acc_minutes:
-#		return '%04d-%02d-%02d %02d:%02d' % (
-#			dt.year,
-#			dt.month,
-#			dt.day,
-#			dt.hour,
-#			dt.minute
-#		)
-#
-#	return '%04d-%02d-%02d %02d:%02d:%02d' % (
-#		dt.year,
-#		dt.month,
-#		dt.day,
-#		dt.hour,
-#		dt.minute,
-#		dt.second
-#	)
 
 #---------------------------------------------------------------------------
 def pydt_add(dt, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0):
@@ -752,24 +724,36 @@ def format_pregnancy_months(age):
 
 #---------------------------------------------------------------------------
 def is_leap_year(year):
+	if year < 1582:		# no leap years before Gregorian Reform
+		_log.debug('%s: before Gregorian Reform', year)
+		return False
+
 	# year is multiple of 4 ?
 	div, remainder = divmod(year, 4)
-	# no -> not a leap year
+	# * NOT divisible by 4
+	# -> common year
 	if remainder > 0:
 		return False
 
 	# year is a multiple of 100 ?
 	div, remainder = divmod(year, 100)
-	# no -> IS a leap year
+	# * divisible by 4
+	# * NOT divisible by 100
+	# -> leap year
 	if remainder > 0:
 		return True
 
 	# year is a multiple of 400 ?
 	div, remainder = divmod(year, 400)
-	# yes -> IS a leap year
+	# * divisible by 4
+	# * divisible by 100, so, perhaps not leaping ?
+	# * but ALSO divisible by 400
+	# -> leap year
 	if remainder == 0:
 		return True
 
+	# all others
+	# -> common year
 	return False
 
 #---------------------------------------------------------------------------
@@ -2406,8 +2390,13 @@ if __name__ == '__main__':
 		print (pydt_strftime(dt, accuracy = acc_seconds))
 	#-------------------------------------------------
 	def test_is_leap_year():
-		for year in range(1995, 2017):
-			print (year, "leaps:", is_leap_year(year))
+		for idx in range(120):
+			year = 1993 + idx
+			tmp, offset = divmod(idx, 4)
+			if is_leap_year(year):
+				print (offset+1, '--', year, 'leaps')
+			else:
+				print (offset+1, '--', year)
 
 	#-------------------------------------------------
 	def test_get_date_of_weekday_in_week_of_date():
@@ -2445,6 +2434,6 @@ if __name__ == '__main__':
 	#test_str2pydt()
 	#test_pydt_strftime()
 	#test_calculate_apparent_age()
-	#test_is_leap_year()
+	test_is_leap_year()
 
 #===========================================================================


=====================================
server/pycommon/gmMimeMagic.py
=====================================
@@ -5,7 +5,7 @@
  (C)opyright 2000 Jason Petrone <jp at demonseed.net>
  All Rights Reserved
 
- Command Line Usage: running as `python magic.py file` will print
+ Command Line Usage: running as `python3 magic.py file` will print
 										 a description of what 'file' is.
 
  Module Usage:


=====================================
server/pycommon/gmPG2.py
=====================================
@@ -54,6 +54,7 @@ except ImportError:
 	raise
 
 import psycopg2.errorcodes as sql_error_codes
+import psycopg2.sql as psysql
 
 PG_ERROR_EXCEPTION = dbapi.Error
 
@@ -261,6 +262,25 @@ WHERE
 		AND
 	indisprimary
 """
+
+SQL_get_primary_key_name = """
+SELECT
+	is_kcu.column_name,
+	is_kcu.ordinal_position
+FROM
+	information_schema.key_column_usage AS is_kcu
+		LEFT JOIN information_schema.table_constraints AS is_tc ON is_tc.constraint_name = is_kcu.constraint_name
+WHERE
+	-- constrain to current database
+	is_tc.table_catalog = current_database()
+		AND
+	is_tc.table_schema = %(schema)s
+		AND
+	is_tc.table_name = %(table)s
+		AND
+	is_tc.constraint_type = 'PRIMARY KEY';
+"""
+
 # =======================================================================
 # login API
 # =======================================================================
@@ -1364,7 +1384,72 @@ def file2bytea_overlay(query=None, args=None, filename=None, conn=None, md5_quer
 	return False
 
 #------------------------------------------------------------------------
-#---------------------------------------------------------------------------
+def read_all_rows_of_table(schema=None, table=None):
+	if schema is None:
+		schema = prompted_input(prompt = 'schema for table to dump', default = None)
+		if schema is None:
+			_log.debug('aborted by user (no schema entered)')
+			return None
+
+	if table is None:
+		table = prompted_input(prompt = 'table to dump (in schema %s.)' % schema, default = None)
+		if table is None:
+			_log.debug('aborted by user (no table entered)')
+			return None
+
+	_log.debug('dumping <%s.%s>', schema, table)
+	conn = get_connection(readonly=True, verbose = False, pooled = True, connection_name = 'read_all_rows_of_table')
+	# get pk column name
+	rows, idx = run_ro_queries(link_obj = conn, queries = [{'cmd': SQL_get_primary_key_name, 'args': {'schema': schema, 'table': table}}])
+	if rows:
+		_log.debug('primary key def: %s', rows)
+		if len(rows) > 1:
+			_log.error('cannot handle multi-column primary key')
+			return False
+
+		pk_name = rows[0][0]
+	else:
+		_log.debug('cannot determine primary key, asking user')
+		pk_name = prompted_input(prompt = 'primary key name for %s.%s' % (schema, table), default = None)
+		if pk_name is None:
+			_log.debug('aborted by user (no primary key name entered)')
+			return None
+
+	# get PK values
+	qualified_table = '%s.%s' % (schema, table)
+	qualified_pk_name = '%s.%s.%s' % (schema, table, pk_name)
+	cmd = psysql.SQL('SELECT {schema_table_pk} FROM {schema_table} ORDER BY 1'.format (
+		schema_table_pk = qualified_pk_name,
+		schema_table = qualified_table
+	))
+	rows, idx = run_ro_queries(link_obj = conn, queries = [{'cmd': cmd}])
+	if not rows:
+		_log.debug('no rows to dump')
+		return True
+
+	# dump table rows
+	_log.debug('dumping %s rows', len(rows))
+	cmd = psysql.SQL('SELECT * FROM {schema_table} WHERE {schema_table_pk} = %(pk_val)s'.format (
+		schema_table = qualified_table,
+		schema_table_pk = qualified_pk_name
+	))
+	found_errors = False
+	idx = 0
+	for row in rows:
+		idx += 1
+		args = {'pk_val': row[0]}
+		_log.debug('dumping row #%s with pk [%s]', idx, row[0])
+		try:
+			run_ro_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}])
+		except dbapi.InternalError:
+			found_errors = True
+			_log.exception('error dumping row')
+			print('ERROR: cannot dump row %s of %s with pk %s = %s', idx, len(rows), qualified_pk_name, rows[0])
+
+	return found_errors is False
+
+#------------------------------------------------------------------------
+#------------------------------------------------------------------------
 def run_sql_script(sql_script, conn=None):
 
 	if conn is None:


=====================================
server/pycommon/gmShellAPI.py
=====================================
@@ -38,6 +38,7 @@ def is_cmd_in_path(cmd=None):
 	_log.debug('command not found in PATH')
 
 	return (False, None)
+
 #===========================================================================
 def is_executable_by_wine(cmd=None):
 
@@ -297,6 +298,7 @@ def run_process(cmd_line=None, timeout=None, encoding='utf8', input_data=None, a
 	except (subprocess.TimeoutExpired, FileNotFoundError):
 		_log.exception('there was a problem running external process')
 		return False, -1, ''
+
 	_log.info('exit code [%s]', proc_result.returncode)
 	if verbose:
 		_log_output(logging.DEBUG, stdout = proc_result.stdout, stderr = proc_result.stderr)
@@ -306,6 +308,7 @@ def run_process(cmd_line=None, timeout=None, encoding='utf8', input_data=None, a
 		if not verbose:
 			_log_output(logging.ERROR, stdout = proc_result.stdout, stderr = proc_result.stderr)
 		return False, proc_result.returncode, ''
+
 	return True, proc_result.returncode, proc_result.stdout
 
 #===========================================================================


=====================================
server/pycommon/gmTools.py
=====================================
@@ -557,24 +557,23 @@ class gmPaths(gmBorg.cBorg):
 def recode_file(source_file=None, target_file=None, source_encoding='utf8', target_encoding=None, base_dir=None, error_mode='replace'):
 	if target_encoding is None:
 		return source_file
+
 	if target_encoding == source_encoding:
 		return source_file
+
 	if target_file is None:
 		target_file = get_unique_filename (
 			prefix = '%s-%s_%s-' % (fname_stem(source_file), source_encoding, target_encoding),
 			suffix = fname_extension(source_file, '.txt'),
 			tmp_dir = base_dir
 		)
-
 	_log.debug('[%s] -> [%s] (%s -> %s)', source_encoding, target_encoding, source_file, target_file)
-
 	in_file = io.open(source_file, mode = 'rt', encoding = source_encoding)
 	out_file = io.open(target_file, mode = 'wt', encoding = target_encoding, errors = error_mode)
 	for line in in_file:
 		out_file.write(line)
 	out_file.close()
 	in_file.close()
-
 	return target_file
 
 #---------------------------------------------------------------------------


=====================================
server/sql/v21-v22/fixups/v22-release_notes-fixup.sql
=====================================
@@ -17,78 +17,25 @@ 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.0 (database v22.11)',
-	'GNUmed 1.8.0 Release Notes:
+	'Release Notes for GNUmed 1.8.1 (database v22.12)',
+	'GNUmed 1.8.1 Release Notes:
 
-	1.8.0
+	1.8.1
 
-NEW: port to wxPython 4 (wxPhoenix)
-NEW: port to Python 3
-NEW: port bootstrapper to Python 3
-NEW: EMR tree: toggle episode status from context menu
-NEW: EMR tree: show/edit clinical items from below encounters
-NEW: ReST formatting in $free_text::::$ placeholder
-NEW: hook "after_waiting_list_modified"
-NEW: test results tab showing most-recent in test panel
-NEW: local documents cache
-NEW: systemd-tmpfiles config file
-NEW: emailing of export area content as encrypted zip file
-NEW: local directory entries in export area
-NEW: $praxis_scan2pay$ support
-NEW: $bill_scan2pay$ support
-NEW: status bar history/visual bell
-NEW: dicomize images/PDF into DICOM study
-NEW: [Abort] client from exception dialog
-NEW: edit clinical item from EMR list journal
-NEW: dist: add PortableApp XML skeleton
-NEW: placeholder: $most_recent_test_results$
-NEW: tool: check_mimetypes_in_archive
+NEW: tool: read_all_rows_of_table
 
-IMPROVED: symbolic link creation on Windows
-IMPROVED: Orthanc connection handling
-IMPROVED: DICOM plugin UI
-IMPROVED: EMR export as TimeLine
-IMPROVED: captions of all list and edit area dialogs
-IMPROVED: test type edit area workflow
-IMPROVED: CLI EMR export tool
-IMPROVED: form disposal dialog
-IMPROVED: date/timestamp picker functionality
-IMPROVED: better duplicate person detection
-IMPROVED: document tree details view usage
-IMPROVED: test results panels links w/ documents
-IMPROVED: console encoding errors behaviour [thanks INADA Naoki]
-IMPROVED: ADR URL handling
-IMPROVED: age sort mode in document tree
-IMPROVED: age/DOB tooltip
-IMPROVED: data revisions display
-IMPROVED: EMR list journal formatting
-IMPROVED: lab/plotting: support better gnuplot scripts
+FIX: bills: failure to generate bill PDFs [thanks Marc]
+FIX: bills: failure to edit bill item date [thanks Marc]
+FIX: bills: failure to edit billable [thanks Marc]
+FIX: export area: fails to load when gm-burn.sh not found [thanks Marc]
+FIX: demographics: failure to edit type of address [thanks Marc]
+FIX: forms: failure to archive generated forms [thanks Marc]
+FIX: demographics: faulty display of patient addresses [thanks Marc]
 
-FIX: [Save] functionality of Export Area
-FIX: document tree sorting / document insertion
-FIX: inability to delete inbox message w/o receiver
-FIX: "lastname, firstname" based patient search under Python 3
-FIX: billing: invoice ID generation [thanks Marc]
-FIX: export area: saving document part entries
-FIX: lab: grid display row tooltips
-FIX: lists: context menu CSV export
-FIX: EMR/tree: selection of pseudo issue node
-FIX: documents/new: error handling of unreadable parts
-FIX: PG access: rewrite connection pool
-FIX: y2038 exception in DST detection
+	22.12
 
-	22.11
-
-FIX: i18n.set_curr/force_curr_lang(), again [thanks lucian]
-
-	22.10
-
-IMPROVED: database fixup script
-
-	22.9
-
-FIX: clin.v_candidate_diagnoses: missing coalesce()
+IMPROVED: robustify backup script
 ');
 
 -- --------------------------------------------------------------
-select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.11');
+select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.12');



View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/compare/58d33e31cc25b3e8a98f34c512c42f0f32d8544f...f3240512fe25d261fd5f7de852611d6ba749f5b3

-- 
View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/compare/58d33e31cc25b3e8a98f34c512c42f0f32d8544f...f3240512fe25d261fd5f7de852611d6ba749f5b3
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/20200325/87367ac1/attachment-0001.html>


More information about the debian-med-commit mailing list