1 """This module encapsulates a document stored in a GNUmed database."""
2
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL v2 or later"
5
6 import sys, os, shutil, os.path, types, time, logging
7
8
9 if __name__ == '__main__':
10 sys.path.insert(0, '../../')
11 from Gnumed.pycommon import gmExceptions
12 from Gnumed.pycommon import gmBusinessDBObject
13 from Gnumed.pycommon import gmPG2
14 from Gnumed.pycommon import gmTools
15 from Gnumed.pycommon import gmMimeLib
16 from Gnumed.pycommon import gmDateTime
17 from Gnumed.pycommon import gmWorkerThread
18
19 from Gnumed.business import gmOrganization
20
21
22 _log = logging.getLogger('gm.docs')
23
24 MUGSHOT=26
25 DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE = 'visual progress note'
26 DOCUMENT_TYPE_PRESCRIPTION = 'prescription'
27
28
30 """Represents a folder with medical documents for a single patient."""
31
33 """Fails if
34
35 - patient referenced by aPKey does not exist
36 """
37 self.pk_patient = aPKey
38 if not self._pkey_exists():
39 raise gmExceptions.ConstructorError("No patient with PK [%s] in database." % aPKey)
40
41
42
43
44
45
46
47 _log.debug('instantiated document folder for patient [%s]' % self.pk_patient)
48
51
52
53
55 """Does this primary key exist ?
56
57 - true/false/None
58 """
59
60 rows, idx = gmPG2.run_ro_queries(queries = [
61 {'cmd': "select exists(select pk from dem.identity where pk = %s)", 'args': [self.pk_patient]}
62 ])
63 if not rows[0][0]:
64 _log.error("patient [%s] not in demographic database" % self.pk_patient)
65 return None
66 return True
67
68
69
71 cmd = """
72 SELECT pk_doc
73 FROM blobs.v_doc_med
74 WHERE
75 pk_patient = %(pat)s
76 AND
77 type = %(typ)s
78 AND
79 ext_ref = %(ref)s
80 ORDER BY
81 clin_when DESC
82 LIMIT 1
83 """
84 args = {
85 'pat': self.pk_patient,
86 'typ': DOCUMENT_TYPE_PRESCRIPTION,
87 'ref': 'FreeDiams'
88 }
89 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
90 if len(rows) == 0:
91 _log.info('no FreeDiams prescription available for patient [%s]' % self.pk_patient)
92 return None
93 prescription = cDocument(aPK_obj = rows[0][0])
94 return prescription
95
96
98 cmd = "SELECT pk_obj FROM blobs.v_latest_mugshot WHERE pk_patient = %s"
99 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
100 if len(rows) == 0:
101 _log.info('no mugshots available for patient [%s]' % self.pk_patient)
102 return None
103 return cDocumentPart(aPK_obj = rows[0][0])
104
105 latest_mugshot = property(get_latest_mugshot, lambda x:x)
106
107
109 if latest_only:
110 cmd = "select pk_doc, pk_obj from blobs.v_latest_mugshot where pk_patient=%s"
111 else:
112 cmd = """
113 select
114 vdm.pk_doc as pk_doc,
115 dobj.pk as pk_obj
116 from
117 blobs.v_doc_med vdm
118 blobs.doc_obj dobj
119 where
120 vdm.pk_type = (select pk from blobs.doc_type where name = 'patient photograph')
121 and vdm.pk_patient = %s
122 and dobj.fk_doc = vdm.pk_doc
123 """
124 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
125 return rows
126
127
129 """return flat list of document IDs"""
130
131 args = {
132 'ID': self.pk_patient,
133 'TYP': doc_type
134 }
135
136 cmd = """
137 select vdm.pk_doc
138 from blobs.v_doc_med vdm
139 where
140 vdm.pk_patient = %%(ID)s
141 %s
142 order by vdm.clin_when"""
143
144 if doc_type is None:
145 cmd = cmd % ''
146 else:
147 try:
148 int(doc_type)
149 cmd = cmd % 'and vdm.pk_type = %(TYP)s'
150 except (TypeError, ValueError):
151 cmd = cmd % 'and vdm.pk_type = (select pk from blobs.doc_type where name = %(TYP)s)'
152
153 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
154 doc_ids = []
155 for row in rows:
156 doc_ids.append(row[0])
157 return doc_ids
158
159
166
167
169 args = {'pat': self.pk_patient}
170 cmd = _SQL_get_document_fields % """
171 pk_doc IN (
172 SELECT DISTINCT ON (b_vo.pk_doc) b_vo.pk_doc
173 FROM blobs.v_obj4doc_no_data b_vo
174 WHERE
175 pk_patient = %(pat)s
176 AND
177 reviewed IS FALSE
178 )
179 ORDER BY clin_when DESC"""
180 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
181 return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
182
183
184 - def get_documents(self, doc_type=None, pk_episodes=None, encounter=None, order_by=None, exclude_unsigned=False, pk_types=None):
185 """Return list of documents."""
186
187 args = {
188 'pat': self.pk_patient,
189 'type': doc_type,
190 'enc': encounter
191 }
192 where_parts = ['pk_patient = %(pat)s']
193
194 if doc_type is not None:
195 try:
196 int(doc_type)
197 where_parts.append('pk_type = %(type)s')
198 except (TypeError, ValueError):
199 where_parts.append('pk_type = (SELECT pk FROM blobs.doc_type WHERE name = %(type)s)')
200
201 if pk_types is not None:
202 where_parts.append('pk_type IN %(pk_types)s')
203 args['pk_types'] = tuple(pk_types)
204
205 if (pk_episodes is not None) and (len(pk_episodes) > 0):
206 where_parts.append('pk_episode IN %(epis)s')
207 args['epis'] = tuple(pk_episodes)
208
209 if encounter is not None:
210 where_parts.append('pk_encounter = %(enc)s')
211
212 if exclude_unsigned:
213 where_parts.append('pk_doc IN (SELECT b_vo.pk_doc FROM blobs.v_obj4doc_no_data b_vo WHERE b_vo.pk_patient = %(pat)s AND b_vo.reviewed IS TRUE)')
214
215 if order_by is None:
216 order_by = 'ORDER BY clin_when'
217
218 cmd = "%s\n%s" % (_SQL_get_document_fields % ' AND '.join(where_parts), order_by)
219 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
220
221 return [ cDocument(row = {'pk_field': 'pk_doc', 'idx': idx, 'data': r}) for r in rows ]
222
223 documents = property(get_documents, lambda x:x)
224
225
226 - def add_document(self, document_type=None, encounter=None, episode=None, link_obj=None):
227 return create_document(link_obj = link_obj, document_type = document_type, encounter = encounter, episode = episode)
228
229
239
240
242 cmd = gmOrganization._SQL_get_org_unit % (
243 'pk_org_unit IN (SELECT DISTINCT ON (pk_org_unit) pk_org_unit FROM blobs.v_doc_med WHERE pk_patient = %(pat)s)'
244 )
245 args = {'pat': self.pk_patient}
246 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
247 return [ gmOrganization.cOrgUnit(row = {'data': r, 'idx': idx, 'pk_field': 'pk_org_unit'}) for r in rows ]
248
249 all_document_org_units = property(_get_all_document_org_units, lambda x:x)
250
251
252 _SQL_get_document_part_fields = "select * from blobs.v_obj4doc_no_data where %s"
253
255 """Represents one part of a medical document."""
256
257 _cmd_fetch_payload = _SQL_get_document_part_fields % "pk_obj = %s"
258 _cmds_store_payload = [
259 """UPDATE blobs.doc_obj SET
260 seq_idx = %(seq_idx)s,
261 comment = gm.nullify_empty_string(%(obj_comment)s),
262 filename = gm.nullify_empty_string(%(filename)s),
263 fk_intended_reviewer = %(pk_intended_reviewer)s
264 WHERE
265 pk = %(pk_obj)s
266 AND
267 xmin = %(xmin_doc_obj)s
268 RETURNING
269 xmin AS xmin_doc_obj"""
270 ]
271 _updatable_fields = [
272 'seq_idx',
273 'obj_comment',
274 'pk_intended_reviewer',
275 'filename'
276 ]
277
278
279
280 - def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
281
282 if filename is None:
283 filename = self.get_useful_filename(make_unique = True, directory = directory)
284
285 filename = self.__download_to_file(filename = filename)
286 if filename is None:
287 return None
288
289 if target_mime is None:
290 if filename.endswith('.dat'):
291 if adjust_extension:
292 return gmMimeLib.adjust_extension_by_mimetype(filename)
293 return filename
294
295 if target_extension is None:
296 target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
297
298 target_path, name = os.path.split(filename)
299 name, tmp = os.path.splitext(name)
300 target_fname = gmTools.get_unique_filename (
301 prefix = '%s-conv-' % name,
302 suffix = target_extension
303 )
304 _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
305 if gmMimeLib.convert_file (
306 filename = filename,
307 target_mime = target_mime,
308 target_filename = target_fname
309 ):
310 return target_fname
311
312 _log.warning('conversion failed')
313 if not ignore_conversion_problems:
314 return None
315
316 if filename.endswith('.dat'):
317 if adjust_extension:
318 filename = gmMimeLib.adjust_extension_by_mimetype(filename)
319 _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
320 return filename
321
322
324 cmd = """
325 SELECT
326 reviewer,
327 reviewed_when,
328 is_technically_abnormal,
329 clinically_relevant,
330 is_review_by_responsible_reviewer,
331 is_your_review,
332 coalesce(comment, '')
333 FROM blobs.v_reviewed_doc_objects
334 WHERE pk_doc_obj = %s
335 ORDER BY
336 is_your_review desc,
337 is_review_by_responsible_reviewer desc,
338 reviewed_when desc
339 """
340 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
341 return rows
342
343
345 return cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
346
347 containing_document = property(__get_containing_document)
348
349
350
351
353
354 if not (os.access(fname, os.R_OK) and os.path.isfile(fname)):
355 _log.error('[%s] is not a readable file' % fname)
356 return False
357
358 if not gmPG2.file2bytea (
359 conn = link_obj,
360 query = "UPDATE blobs.doc_obj SET data = %(data)s::bytea WHERE pk = %(pk)s RETURNING md5(data) AS md5",
361 filename = fname,
362 args = {'pk': self.pk_obj},
363 file_md5 = gmTools.file2md5(filename = fname, return_hex = True)
364 ):
365 return False
366
367
368 self.refetch_payload(link_obj = link_obj)
369 return True
370
371
372 - def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
373
374 cmd = """
375 select pk
376 from blobs.reviewed_doc_objs
377 where
378 fk_reviewed_row = %s and
379 fk_reviewer = (select pk from dem.staff where db_user = current_user)"""
380 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
381
382
383 if len(rows) == 0:
384 cols = [
385 "fk_reviewer",
386 "fk_reviewed_row",
387 "is_technically_abnormal",
388 "clinically_relevant"
389 ]
390 vals = [
391 '%(fk_row)s',
392 '%(abnormal)s',
393 '%(relevant)s'
394 ]
395 args = {
396 'fk_row': self.pk_obj,
397 'abnormal': technically_abnormal,
398 'relevant': clinically_relevant
399 }
400 cmd = """
401 insert into blobs.reviewed_doc_objs (
402 %s
403 ) values (
404 (select pk from dem.staff where db_user=current_user),
405 %s
406 )""" % (', '.join(cols), ', '.join(vals))
407
408
409 if len(rows) == 1:
410 pk_review = rows[0][0]
411 args = {
412 'abnormal': technically_abnormal,
413 'relevant': clinically_relevant,
414 'pk_review': pk_review
415 }
416 cmd = """
417 UPDATE blobs.reviewed_doc_objs SET
418 is_technically_abnormal = %(abnormal)s,
419 clinically_relevant = %(relevant)s
420 WHERE
421 pk = %(pk_review)s
422 """
423 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
424
425 return True
426
427
429 if self._payload[self._idx['type']] != 'patient photograph':
430 return False
431
432 cmd = 'SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s'
433 rows, idx = gmPG2.run_ro_queries (
434 queries = [{
435 'cmd': cmd,
436 'args': {'doc_id': self._payload[self._idx['pk_doc']]}
437 }]
438 )
439 self._payload[self._idx['seq_idx']] = rows[0][0]
440 self._is_modified = True
441 self.save_payload()
442
443
445 if pk_doc == self._payload[self._idx['pk_doc']]:
446 return True
447
448 cmd = """
449 UPDATE blobs.doc_obj SET
450 fk_doc = %(pk_doc_target)s,
451 -- coalesce needed for no-parts target docs
452 seq_idx = (SELECT coalesce(max(seq_idx) + 1, 1) FROM blobs.doc_obj WHERE fk_doc = %(pk_doc_target)s)
453 WHERE
454 EXISTS(SELECT 1 FROM blobs.doc_med WHERE pk = %(pk_doc_target)s)
455 AND
456 pk = %(pk_obj)s
457 AND
458 xmin = %(xmin_doc_obj)s
459 RETURNING fk_doc
460 """
461 args = {
462 'pk_doc_target': pk_doc,
463 'pk_obj': self.pk_obj,
464 'xmin_doc_obj': self._payload[self._idx['xmin_doc_obj']]
465 }
466 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
467 if len(rows) == 0:
468 return False
469
470
471
472
473
474
475 if rows[0]['fk_doc'] == self._payload[self._idx['pk_doc']]:
476 return False
477
478 self.refetch_payload()
479 return True
480
481
483
484 fname = self.save_to_file(aChunkSize = chunksize)
485 if fname is None:
486 return False, ''
487
488 success, msg = gmMimeLib.call_viewer_on_file(fname, block = block)
489 if not success:
490 return False, msg
491
492 return True, ''
493
494
511
512
547
548
559
560
561 - def get_useful_filename(self, patient=None, make_unique=False, directory=None, include_gnumed_tag=True, date_before_type=False, name_first=True):
562 patient_part = ''
563 if patient is not None:
564 if name_first:
565 patient_part = '%s-' % patient.subdir_name
566 else:
567 patient_part = '-%s' % patient.subdir_name
568
569
570 suffix = '.dat'
571 if self._payload[self._idx['filename']] is not None:
572 tmp, suffix = os.path.splitext (
573 gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower()
574 )
575 if suffix == '':
576 suffix = '.dat'
577
578 if include_gnumed_tag:
579 fname_template = 'gm_doc-part_%s-%%s' % self._payload[self._idx['seq_idx']]
580 else:
581 fname_template = '%%s-part_%s' % self._payload[self._idx['seq_idx']]
582
583 if date_before_type:
584 date_type_part = '%s-%s' % (
585 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days),
586 self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
587 )
588 else:
589 date_type_part = '%s-%s' % (
590 self._payload[self._idx['l10n_type']].replace(' ', '_').replace('-', '_'),
591 gmDateTime.pydt_strftime(self._payload[self._idx['date_generated']], '%Y-%m-%d', 'utf-8', gmDateTime.acc_days)
592 )
593
594 if name_first:
595 date_type_name_part = patient_part + date_type_part
596 else:
597 date_type_name_part = date_type_part + patient_part
598
599 fname = fname_template % date_type_name_part
600
601 if make_unique:
602 fname = gmTools.get_unique_filename (
603 prefix = '%s-' % gmTools.fname_sanitize(fname),
604 suffix = suffix,
605 tmp_dir = directory
606 )
607 else:
608 fname = gmTools.fname_sanitize(os.path.join(gmTools.coalesce(directory, gmTools.gmPaths().tmp_dir), fname + suffix))
609
610 return fname
611
612 useful_filename = property(get_useful_filename)
613
614
615
616
618 if self._payload[self._idx['size']] == 0:
619 _log.debug('part size 0, nothing to download')
620 return None
621
622 if filename is None:
623 filename = gmTools.get_unique_filename()
624 success = gmPG2.bytea2file (
625 data_query = {
626 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM blobs.doc_obj WHERE pk=%(pk)s',
627 'args': {'pk': self.pk_obj}
628 },
629 filename = filename,
630 chunk_size = aChunkSize,
631 data_size = self._payload[self._idx['size']],
632 conn = conn
633 )
634 if not success:
635 return None
636
637 return filename
638
639
648
649
651 cmd = """
652 SELECT blobs.delete_document_part(%(pk)s, %(enc)s)
653 WHERE NOT EXISTS
654 (SELECT 1 FROM clin.export_item where fk_doc_obj = %(pk)s)
655 """
656 args = {'pk': part_pk, 'enc': encounter_pk}
657 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
658 return
659
660
661 _SQL_get_document_fields = "SELECT * FROM blobs.v_doc_med b_vdm WHERE %s"
662
663 -class cDocument(gmBusinessDBObject.cBusinessDBObject):
664 """Represents one medical document."""
665
666 _cmd_fetch_payload = _SQL_get_document_fields % "pk_doc = %s"
667 _cmds_store_payload = [
668 """UPDATE blobs.doc_med SET
669 fk_type = %(pk_type)s,
670 fk_episode = %(pk_episode)s,
671 fk_encounter = %(pk_encounter)s,
672 fk_org_unit = %(pk_org_unit)s,
673 unit_is_receiver = %(unit_is_receiver)s,
674 clin_when = %(clin_when)s,
675 comment = gm.nullify_empty_string(%(comment)s),
676 ext_ref = gm.nullify_empty_string(%(ext_ref)s),
677 fk_hospital_stay = %(pk_hospital_stay)s
678 WHERE
679 pk = %(pk_doc)s and
680 xmin = %(xmin_doc_med)s
681 RETURNING
682 xmin AS xmin_doc_med"""
683 ]
684 _updatable_fields = [
685 'pk_type',
686 'comment',
687 'clin_when',
688 'ext_ref',
689 'pk_episode',
690 'pk_encounter',
691 'pk_org_unit',
692 'unit_is_receiver',
693 'pk_hospital_stay'
694 ]
695
696
698 try: del self.__has_unreviewed_parts
699 except AttributeError: pass
700
701 return super(cDocument, self).refetch_payload(ignore_changes = ignore_changes, link_obj = link_obj)
702
703
705 """Get document descriptions.
706
707 - will return a list of rows
708 """
709 if max_lng is None:
710 cmd = "SELECT pk, text FROM blobs.doc_desc WHERE fk_doc = %s"
711 else:
712 cmd = "SELECT pk, substring(text from 1 for %s) FROM blobs.doc_desc WHERE fk_doc=%%s" % max_lng
713 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
714 return rows
715
716
721
722
724 cmd = "update blobs.doc_desc set text = %(desc)s where fk_doc = %(doc)s and pk = %(pk_desc)s"
725 gmPG2.run_rw_queries(queries = [
726 {'cmd': cmd, 'args': {'doc': self.pk_obj, 'pk_desc': pk, 'desc': description}}
727 ])
728 return True
729
730
732 cmd = "delete from blobs.doc_desc where fk_doc = %(doc)s and pk = %(desc)s"
733 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'doc': self.pk_obj, 'desc': pk}}])
734 return True
735
736
741
742 parts = property(_get_parts, lambda x:x)
743
744
745 - def add_part(self, file=None, link_obj=None):
746 """Add a part to the document."""
747
748 cmd = """
749 INSERT INTO blobs.doc_obj (
750 fk_doc, data, seq_idx
751 ) VALUES (
752 %(doc_id)s,
753 ''::bytea,
754 (SELECT coalesce(max(seq_idx)+1, 1) FROM blobs.doc_obj WHERE fk_doc = %(doc_id)s)
755 ) RETURNING pk"""
756 rows, idx = gmPG2.run_rw_queries (
757 link_obj = link_obj,
758 queries = [{'cmd': cmd, 'args': {'doc_id': self.pk_obj}}],
759 return_data = True
760 )
761
762 pk_part = rows[0][0]
763 new_part = cDocumentPart(aPK_obj = pk_part, link_obj = link_obj)
764 if not new_part.update_data_from_file(link_obj = link_obj, fname = file):
765 _log.error('cannot import binary data from [%s] into document part' % file)
766 gmPG2.run_rw_queries (
767 link_obj = link_obj,
768 queries = [{'cmd': "DELETE FROM blobs.doc_obj WHERE pk = %s", 'args': [pk_part]}]
769 )
770 return None
771 new_part['filename'] = file
772 new_part.save_payload(conn = link_obj)
773
774 return new_part
775
776
778
779 new_parts = []
780
781 for filename in files:
782 new_part = self.add_part(file = filename)
783 if new_part is None:
784 msg = 'cannot instantiate document part object from [%s]' % filename
785 _log.error(msg)
786 return (False, msg, filename)
787 new_parts.append(new_part)
788
789 if reviewer is not None:
790 new_part['pk_intended_reviewer'] = reviewer
791 success, data = new_part.save_payload()
792 if not success:
793 msg = 'cannot set reviewer to [%s] on [%s]' % (reviewer, filename)
794 _log.error(msg)
795 _log.error(str(data))
796 return (False, msg, filename)
797
798 return (True, '', new_parts)
799
800
802 fnames = []
803 for part in self.parts:
804 fname = part.save_to_file(aChunkSize = chunksize, directory = export_dir, conn = conn)
805 if fname is None:
806 _log.error('cannot export document part [%s]', part)
807 continue
808 fnames.append(fname)
809 return fnames
810
811
813 try:
814 return self.__has_unreviewed_parts
815 except AttributeError:
816 pass
817
818 cmd = "SELECT EXISTS(SELECT 1 FROM blobs.v_obj4doc_no_data WHERE pk_doc = %(pk)s AND reviewed IS FALSE)"
819 args = {'pk': self.pk_obj}
820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
821 self.__has_unreviewed_parts = rows[0][0]
822
823 return self.__has_unreviewed_parts
824
825 has_unreviewed_parts = property(_get_has_unreviewed_parts, lambda x:x)
826
827
828 - def set_reviewed(self, technically_abnormal=None, clinically_relevant=None):
829
830 for part in self.parts:
831 if not part.set_reviewed(technically_abnormal, clinically_relevant):
832 return False
833 return True
834
835
837 for part in self.parts:
838 part['pk_intended_reviewer'] = reviewer
839 success, data = part.save_payload()
840 if not success:
841 _log.error('cannot set reviewer to [%s]' % reviewer)
842 _log.error(str(data))
843 return False
844 return True
845
846
882
883
939
940
946
947 hospital_stay = property(_get_hospital_stay, lambda x:x)
948
949
951 if self._payload[self._idx['pk_org_unit']] is None:
952 return None
953 return gmOrganization.cOrgUnit(self._payload[self._idx['pk_org_unit']])
954
955 org_unit = property(_get_org_unit, lambda x:x)
956
957
961
962 procedures = property(_get_procedures, lambda x:x)
963
964
968
969 bills = property(_get_bills, lambda x:x)
970
971
972 -def create_document(document_type=None, encounter=None, episode=None, link_obj=None):
973 """Returns new document instance or raises an exception."""
974 try:
975 int(document_type)
976 cmd = """INSERT INTO blobs.doc_med (fk_type, fk_encounter, fk_episode) VALUES (%(type)s, %(enc)s, %(epi)s) RETURNING pk"""
977 except ValueError:
978 create_document_type(document_type = document_type)
979 cmd = """
980 INSERT INTO blobs.doc_med (
981 fk_type,
982 fk_encounter,
983 fk_episode
984 ) VALUES (
985 coalesce (
986 (SELECT pk from blobs.doc_type bdt where bdt.name = %(type)s),
987 (SELECT pk from blobs.doc_type bdt where _(bdt.name) = %(type)s)
988 ),
989 %(enc)s,
990 %(epi)s
991 ) RETURNING pk"""
992 args = {'type': document_type, 'enc': encounter, 'epi': episode}
993 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True)
994 doc = cDocument(aPK_obj = rows[0][0], link_obj = link_obj)
995 return doc
996
997
998 -def search_for_documents(patient_id=None, type_id=None, external_reference=None, pk_episode=None, pk_types=None):
999 """Searches for documents with the given patient and type ID."""
1000
1001 if (patient_id is None) and (pk_episode is None):
1002 raise ValueError('need patient_id or pk_episode to search for document')
1003
1004 where_parts = []
1005 args = {
1006 'pat_id': patient_id,
1007 'type_id': type_id,
1008 'ref': external_reference,
1009 'pk_epi': pk_episode
1010 }
1011
1012 if patient_id is not None:
1013 where_parts.append('pk_patient = %(pat_id)s')
1014
1015 if type_id is not None:
1016 where_parts.append('pk_type = %(type_id)s')
1017
1018 if external_reference is not None:
1019 where_parts.append('ext_ref = %(ref)s')
1020
1021 if pk_episode is not None:
1022 where_parts.append('pk_episode = %(pk_epi)s')
1023
1024 if pk_types is not None:
1025 where_parts.append('pk_type IN %(pk_types)s')
1026 args['pk_types'] = tuple(pk_types)
1027
1028 cmd = _SQL_get_document_fields % ' AND '.join(where_parts)
1029 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1030 return [ cDocument(row = {'data': r, 'idx': idx, 'pk_field': 'pk_doc'}) for r in rows ]
1031
1032
1034
1035 cmd = "SELECT blobs.delete_document(%(pk)s, %(enc)s)"
1036 args = {'pk': document_id, 'enc': encounter_id}
1037 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
1038 if not rows[0][0]:
1039 _log.error('cannot delete document [%s]', document_id)
1040 return False
1041 return True
1042
1043
1045
1046 _log.debug('reclassifying documents by type')
1047 _log.debug('original: %s', original_type)
1048 _log.debug('target: %s', target_type)
1049
1050 if target_type['pk_doc_type'] == original_type['pk_doc_type']:
1051 return True
1052
1053 cmd = """
1054 update blobs.doc_med set
1055 fk_type = %(new_type)s
1056 where
1057 fk_type = %(old_type)s
1058 """
1059 args = {'new_type': target_type['pk_doc_type'], 'old_type': original_type['pk_doc_type']}
1060
1061 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1062
1063 return True
1064
1065
1067 """Represents a document type."""
1068 _cmd_fetch_payload = """select * from blobs.v_doc_type where pk_doc_type=%s"""
1069 _cmds_store_payload = [
1070 """update blobs.doc_type set
1071 name = %(type)s
1072 where
1073 pk=%(pk_obj)s and
1074 xmin=%(xmin_doc_type)s""",
1075 """select xmin_doc_type from blobs.v_doc_type where pk_doc_type = %(pk_obj)s"""
1076 ]
1077 _updatable_fields = ['type']
1078
1080
1081 if translation.strip() == '':
1082 return False
1083
1084 if translation.strip() == self._payload[self._idx['l10n_type']].strip():
1085 return True
1086
1087 rows, idx = gmPG2.run_rw_queries (
1088 queries = [
1089 {'cmd': 'select i18n.i18n(%s)', 'args': [self._payload[self._idx['type']]]},
1090 {'cmd': 'select i18n.upd_tx((select i18n.get_curr_lang()), %(orig)s, %(tx)s)',
1091 'args': {
1092 'orig': self._payload[self._idx['type']],
1093 'tx': translation
1094 }
1095 }
1096 ],
1097 return_data = True
1098 )
1099 if not rows[0][0]:
1100 _log.error('cannot set translation to [%s]' % translation)
1101 return False
1102
1103 return self.refetch_payload()
1104
1105
1107 rows, idx = gmPG2.run_ro_queries (
1108 queries = [{'cmd': "SELECT * FROM blobs.v_doc_type"}],
1109 get_col_idx = True
1110 )
1111 doc_types = []
1112 for row in rows:
1113 row_def = {'pk_field': 'pk_doc_type', 'idx': idx, 'data': row}
1114 doc_types.append(cDocumentType(row = row_def))
1115 return doc_types
1116
1117
1119 args = {'typ': document_type.strip()}
1120
1121 cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %(typ)s'
1122 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1123 if len(rows) == 0:
1124 cmd = 'SELECT pk FROM blobs.doc_type WHERE _(name) = %(typ)s'
1125 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1126
1127 if len(rows) == 0:
1128 return None
1129
1130 return rows[0]['pk']
1131
1132
1134 args = {'types': tuple(document_types)}
1135 cmd = 'SELECT pk_doc_type, coalesce(l10n_type, type) as desc FROM blobs.v_doc_type WHERE l10n_type IN %(types)s OR type IN %(types)s'
1136 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1137 return rows
1138
1139
1141
1142 cmd = 'SELECT pk FROM blobs.doc_type WHERE name = %s'
1143 rows, idx = gmPG2.run_ro_queries (
1144 queries = [{'cmd': cmd, 'args': [document_type]}]
1145 )
1146 if len(rows) == 0:
1147 _log.debug('creating document type [%s]', document_type)
1148 cmd1 = "INSERT INTO blobs.doc_type (name) VALUES (%s) RETURNING pk"
1149 rows, idx = gmPG2.run_rw_queries (
1150 queries = [{'cmd': cmd1, 'args': [document_type]}],
1151 return_data = True
1152 )
1153 return cDocumentType(aPK_obj = rows[0][0])
1154
1155
1157 if document_type['is_in_use']:
1158 return False
1159 gmPG2.run_rw_queries (
1160 queries = [{
1161 'cmd': 'delete from blobs.doc_type where pk=%s',
1162 'args': [document_type['pk_doc_type']]
1163 }]
1164 )
1165 return True
1166
1167
1169 """This needs *considerably* more smarts."""
1170 dirname = gmTools.get_unique_filename (
1171 prefix = '',
1172 suffix = time.strftime(".%Y%m%d-%H%M%S", time.localtime())
1173 )
1174
1175 path, doc_ID = os.path.split(dirname)
1176 return doc_ID
1177
1178
1179
1180
1181 if __name__ == '__main__':
1182
1183 if len(sys.argv) < 2:
1184 sys.exit()
1185
1186 if sys.argv[1] != 'test':
1187 sys.exit()
1188
1189
1191
1192 print("----------------------")
1193 print("listing document types")
1194 print("----------------------")
1195
1196 for dt in get_document_types():
1197 print(dt)
1198
1199 print("------------------------------")
1200 print("testing document type handling")
1201 print("------------------------------")
1202
1203 dt = create_document_type(document_type = 'dummy doc type for unit test 1')
1204 print("created:", dt)
1205
1206 dt['type'] = 'dummy doc type for unit test 2'
1207 dt.save_payload()
1208 print("changed base name:", dt)
1209
1210 dt.set_translation(translation = 'Dummy-Dokumenten-Typ fuer Unit-Test')
1211 print("translated:", dt)
1212
1213 print("deleted:", delete_document_type(document_type = dt))
1214
1215 return
1216
1218
1219 print("-----------------------")
1220 print("testing document import")
1221 print("-----------------------")
1222
1223 docs = search_for_documents(patient_id=12)
1224 doc = docs[0]
1225 print("adding to doc:", doc)
1226
1227 fname = sys.argv[1]
1228 print("adding from file:", fname)
1229 part = doc.add_part(file=fname)
1230 print("new part:", part)
1231
1232 return
1233
1235
1236 doc_folder = cDocumentFolder(aPKey=12)
1237
1238
1239
1240
1241 docs = doc_folder.get_documents()
1242 for doc in docs:
1243
1244
1245
1246 print('--------------------------')
1247 print(doc.format(single_line = True))
1248 print(doc.format())
1249
1250
1266
1267
1278
1279
1280 pk = 12
1281 from Gnumed.business.gmPerson import cPatient
1282 pat = cPatient(pk)
1283 doc_folder = cDocumentFolder(aPKey = pk)
1284 for doc in doc_folder.documents:
1285 for part in doc.parts:
1286 part.format_metainfo(callback = desc_printer)
1287 input('waiting ...')
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303 from Gnumed.pycommon import gmI18N
1304 gmI18N.activate_locale()
1305 gmI18N.install_domain()
1306
1307
1308
1309
1310
1311 test_part_metainfo_formatter()
1312
1313
1314