Skip to content
Commits on Source (5)
......@@ -6,6 +6,18 @@
# rel-1-7-patches
------------------------------------------------
1.7.7
FIX: EMR/tree: exception on showing visual progress note
FIX: lab/result EA: exception when no previous result available
FIX: meds/substance EA: exception when no LOINC selected
FIX: data/ATC: fix reference data import
FIX: meds/dose EA: exception on saving
FIX: meds/product EA: exception on creating new product
FIX: dist: fix appdata.xml [thanks Andreas]
NEW: configurable invoice ID template [thanks Marc]
1.7.6
FIX: application metadata files
......@@ -1997,6 +2009,13 @@ FIX: missing cast to ::text in dem.date_trunc_utc() calls
# gnumed_v22
------------------------------------------------
22.7
IMPROVED: lab results plotting scripts for gnuplot
IMPROVED: bills tables grants for invoice ID generation
NEW: multi-results plotting script for gnuplot
22.6
FIX: properly include fixups in v21-v22 upgrade
......
......@@ -35,7 +35,7 @@
<screenshot type="default">
<image>https://screenshots.debian.net/screenshots/000/002/117/large.png</image>
<caption>The main window showing one of the ways to enter Progress Notes during a patient encounter</caption>
</screenshot
</screenshot>
</screenshots>
<url type="homepage">https://www.gnumed.de</url>
<update_contact>gnumed-devel_AT_gnu.org</update_contact>
......
......@@ -148,28 +148,35 @@ def atc_import(cfg_fname=None, conn=None):
args = {'ver': version, 'desc': desc, 'url': url, 'name_long': name_long, 'name_short': name_short, 'lang': lang}
# create data source record
queries = [
{
'cmd': u"""delete from ref.data_source where name_short = %(name_short)s and version = %(ver)s""",
'args': args
}, {
'cmd': u"""
insert into ref.data_source (name_long, name_short, version, description, lang, source) values (
# find or create data source record
cmd = u"select pk from ref.data_source where name_short = %(name_short)s and version = %(ver)s"
rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
if len(rows) > 0:
data_src_pk = rows[0][0]
_log.debug('ATC data source record existed, pk is #%s, refreshing fields', data_src_pk)
# exists - update
args['pk'] = data_src_pk
cmd = u"""UPDATE ref.data_source SET
name_long = %(name_long)s,
description = %(desc)s,
lang = %(lang)s,
source = %(url)s
WHERE
pk = %(pk)s
"""
rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
else:
_log.debug('ATC data source record not found, creating')
# create
cmd = u"""insert into ref.data_source (name_long, name_short, version, description, lang, source) values (
%(name_long)s,
%(name_short)s,
%(ver)s,
%(desc)s,
%(lang)s,
%(url)s
)""",
'args': args
}, {
'cmd': u"""select pk from ref.data_source where name_short = %(name_short)s and version = %(ver)s""",
'args': args
}
]
rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
) returning pk"""
rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
data_src_pk = rows[0][0]
_log.debug('ATC data source record created, pk is #%s', data_src_pk)
......@@ -235,30 +242,45 @@ insert into ref.data_source (name_long, name_short, version, description, lang,
_log.debug('ATC staging table loaded')
# from staging table to real table
curs = conn.cursor()
args = {'src_pk': data_src_pk}
queries = []
# transfer new records
cmd = u"""
insert into ref.atc (
fk_data_source,
code,
term,
comment,
unit,
administration_route
) select
%(src_pk)s,
atc,
name,
nullif(comment, ''),
nullif(unit, ''),
nullif(adro, '')
from
FROM
ref.atc_staging
WHERE
not exists (
select 1 FROM ref.atc WHERE fk_data_source = %(src_pk)s AND code = ref.atc_staging.atc
)
"""
gmPG2.run_rw_queries(link_obj = curs, queries = [{'cmd': cmd, 'args': args}])
queries.append({'cmd': cmd, 'args': args})
# update records so pre-existing ones are refreshed
cmd = u"""
UPDATE ref.atc SET
code = r_as.atc,
term = r_as.name,
comment = nullif(r_as.comment, ''),
administration_route = nullif(r_as.adro, '')
FROM
(SELECT atc, name, comment, adro FROM ref.atc_staging) AS r_as
WHERE
fk_data_source = %(src_pk)s
"""
queries.append({'cmd': cmd, 'args': args})
curs = conn.cursor()
gmPG2.run_rw_queries(link_obj = curs, queries = queries)
curs.close()
conn.commit()
_log.debug('transfer from ATC staging table to real ATC table done')
......
......@@ -9,6 +9,7 @@ __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
import sys
import logging
import zlib
if __name__ == '__main__':
......@@ -17,12 +18,14 @@ from Gnumed.pycommon import gmPG2
from Gnumed.pycommon import gmBusinessDBObject
from Gnumed.pycommon import gmTools
from Gnumed.pycommon import gmDateTime
from Gnumed.business import gmDemographicRecord
from Gnumed.business import gmDocuments
_log = logging.getLogger('gm.bill')
INVOICE_DOCUMENT_TYPE = u'invoice'
#============================================================
# billables
#------------------------------------------------------------
......@@ -540,14 +543,114 @@ def get_bill_receiver(pk_patient=None):
pass
#------------------------------------------------------------
def get_invoice_id(pk_patient=None):
return u'GM%s / %s' % (
pk_patient,
gmDateTime.pydt_strftime (
gmDateTime.pydt_now_here(),
'%Y-%m-%d / %H%M%S'
)
)
def generate_invoice_id(template=None, pk_patient=None, person=None, date_format='%Y-%m-%d', time_format='%H%M%S'):
"""Generate invoice ID string, based on template.
No template given -> generate old style fixed format invoice ID.
Placeholders:
%(pk_pat)s
%(date)s
%(time)s
if included, $counter$ is not *needed* (but still possible)
%(firstname)s
%(lastname)s
%(dob)s
#counter#
will be replaced by a counter, counting up from 1 until the invoice id is unique, max 999999
"""
assert (None in [pk_patient, person]), u'either of <pk_patient> or <person> can be defined, but not both'
if (template is None) or (template.strip() == u''):
# force old style
template = u'GM%(pk_pat)s / %(date)s / %(time)s'
date_format = '%Y-%m-%d'
time_format = '%H%M%S'
template = template.strip()
_log.debug('invoice ID template: %s', template)
if pk_patient is None:
if person is not None:
pk_patient = person.ID
now = gmDateTime.pydt_now_here()
data = {}
data['pk_pat'] = gmTools.coalesce(pk_patient, '?')
data['date'] = gmDateTime.pydt_strftime(now, date_format).strip()
data['time'] = gmDateTime.pydt_strftime(now, time_format).strip()
if person is None:
data['firstname'] = u'?'
data['lastname'] = u'?'
data['dob'] = u'?'
else:
data['firstname'] = person['firstnames'].replace(' ', gmTools.u_space_as_open_box).strip()
data['lastname'] = person['lastnames'].replace(' ', gmTools.u_space_as_open_box).strip()
data['dob'] = person.get_formatted_dob (
format = date_format,
encoding = 'utf8',
none_string = u'?',
honor_estimation = False
).strip()
candidate_invoice_id = template % data
if u'#counter#' not in candidate_invoice_id:
if u'%(time)s' in template:
return candidate_invoice_id
candidate_invoice_id = candidate_invoice_id + u' [##counter#]'
_log.debug('invoice id candidate: %s', candidate_invoice_id)
# get existing invoice IDs consistent with candidate
search_term = u'^\s*%s\s*$' % gmPG2.sanitize_pg_regex(expression = candidate_invoice_id).replace(u'#counter#', '\d+')
cmd = u'SELECT invoice_id FROM bill.bill WHERE invoice_id ~* %(search_term)s UNION ALL SELECT invoice_id FROM audit.log_bill WHERE invoice_id ~* %(search_term)s'
args = {'search_term': search_term}
rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
if len(rows) == 0:
return candidate_invoice_id.replace(u'#counter#', u'1')
existing_invoice_ids = [ r['invoice_id'].strip() for r in rows ]
counter = None
counter_max = 999999
for idx in range(1, counter_max):
candidate = candidate_invoice_id.replace(u'#counter#', '%s' % idx)
if candidate not in existing_invoice_ids:
counter = idx
break
if counter is None:
# exhausted the range, unlikely (1 million bills are possible
# even w/o any other invoice ID data) but technically possible
_log.debug('exhausted uniqueness space of [%s] invoice IDs per template', counter_max)
counter = '>%s[%s]' % (counter_max, data['time'])
return candidate_invoice_id.replace(u'#counter#', '%s' % counter)
#------------------------------------------------------------
def lock_invoice_id(invoice_id):
_log.debug('locking invoice ID: %s', invoice_id)
crc32 = zlib.crc32(invoice_id)
adler32 = zlib.adler32(invoice_id)
_log.debug('crc32: %s', crc32)
_log.debug('adler32: %s', adler32)
cmd = u"""SELECT pg_try_advisory_lock(%s, %s)""" % (crc32, adler32)
rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
if rows[0][0]:
return True
_log.warning('cannot lock invoice ID: [%s] (%s/%s)', invoice_id, crc32, adler32)
return False
#------------------------------------------------------------
def unlock_invoice_id(invoice_id):
_log.debug('unlocking invoice ID: %s', invoice_id)
crc32 = zlib.crc32(invoice_id)
adler32 = zlib.adler32(invoice_id)
_log.debug('crc32: %s', crc32)
_log.debug('adler32: %s', adler32)
cmd = u"""SELECT pg_advisory_unlock(%s, %s)""" % (crc32, adler32)
rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
if rows[0][0]:
return True
_log.warning('cannot unlock invoice ID: [%s] (%s/%s)', invoice_id, crc32, adler32)
return False
#============================================================
# main
......@@ -572,6 +675,7 @@ if __name__ == "__main__":
first_bill = bills[0]
print first_bill.default_address
#--------------------------------------------------
def test_me():
print "--------------"
me = cBillable(aPK_obj=1)
......@@ -580,6 +684,44 @@ if __name__ == "__main__":
print field, ':', me[field]
print "updatable:", me.get_updatable_fields()
#me['vat']=4; me.store_payload()
#--------------------------------------------------
def test_generate_invoice_id():
from Gnumed.pycommon import gmI18N
gmI18N.activate_locale()
gmI18N.install_domain()
import gmPerson
for idx in range(1,15):
print ''
print 'classic:', generate_invoice_id(pk_patient = idx)
pat = gmPerson.cPerson(idx)
template = u'%(firstname).4s%(lastname).4s%(date)s'
print 'new: template = "%s" => %s' % (
template,
generate_invoice_id (
template = template,
pk_patient = None,
person = pat,
date_format='%d%m%Y',
time_format='%H%M%S'
)
)
template = u'%(firstname).4s%(lastname).4s%(date)s-#counter#'
print 'new: template = "%s" => %s' % (
template,
generate_invoice_id (
template = template,
pk_patient = None,
person = pat,
date_format='%d%m%Y',
time_format='%H%M%S'
)
)
#generate_invoice_id(template=None, pk_patient=None, person=None, date_format='%Y-%m-%d', time_format='%H%M%S')
#--------------------------------------------------
#test_me()
test_default_address()
test_generate_invoice_id()
......@@ -1739,25 +1739,30 @@ def get_drug_by_atc(atc=None, preparation=None, link_obj=None):
#------------------------------------------------------------
def create_drug_product(product_name=None, preparation=None, return_existing=False, link_obj=None, doses=None):
if preparation is None:
preparation = _('units')
if preparation.strip() == u'':
preparation = _('units')
if return_existing:
drug = get_drug_by_name(product_name = product_name, preparation = preparation, link_obj = link_obj)
if drug is not None:
return drug
if link_obj is None:
link_obj = gmPG2.get_connection(readonly = False)
conn_commit = link_obj.commit
conn_close = link_obj.close
else:
conn_commit = lambda x:x
conn_close = lambda x:x
cmd = u'INSERT INTO ref.drug_product (description, preparation) VALUES (%(prod_name)s, %(prep)s) RETURNING pk'
args = {'prod_name': product_name, 'prep': preparation}
rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
product = cDrugProduct(aPK_obj = rows[0]['pk'], link_obj = link_obj)
if doses is not None:
product.set_substance_doses_as_components(substance_doses = doses, link_obj = link_obj)
conn_commit()
conn_close()
return product
#------------------------------------------------------------
......
This diff is collapsed.
......@@ -98,7 +98,7 @@ expandto(location.href);
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -123,7 +123,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -99,7 +99,7 @@ expandto(location.href);
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -154,7 +154,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -125,7 +125,7 @@ expandto(location.href);
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -154,7 +154,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -131,7 +131,7 @@ expandto(location.href);
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -167,7 +167,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -99,7 +99,7 @@ expandto(location.href);
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -232,7 +232,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -233,7 +233,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......
......@@ -131,6 +131,7 @@
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#get_patient">get_patient</a></code>,
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#get_updatable_fields">get_updatable_fields</a></code>,
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#is_modified">is_modified</a></code>,
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#patient_pk">patient_pk</a></code>,
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#refetch_payload">refetch_payload</a></code>,
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#same_payload">same_payload</a></code>,
<code><a href="Gnumed.pycommon.gmBusinessDBObject.cBusinessDBObject-class.html#save">save</a></code>,
......@@ -259,7 +260,7 @@
<tr>
<td align="left" class="footer">
Generated by Epydoc 3.0.1
on Sun Jul 28 01:55:29 2019
on Fri Sep 13 01:55:28 2019
</td>
<td align="right" class="footer">
<a target="mainFrame" href="http://epydoc.sourceforge.net"
......