1 """GNUmed medication/substances handling widgets."""
2
3
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later"
6
7 import logging
8 import sys
9 import os.path
10 import decimal
11
12
13 import wx
14 import wx.grid
15
16
17 if __name__ == '__main__':
18 sys.path.insert(0, '../../')
19 from Gnumed.pycommon import gmDispatcher
20 from Gnumed.pycommon import gmCfg
21 from Gnumed.pycommon import gmTools
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmDateTime
24 from Gnumed.pycommon import gmMatchProvider
25 from Gnumed.pycommon import gmI18N
26 from Gnumed.pycommon import gmPrinting
27 from Gnumed.pycommon import gmCfg2
28 from Gnumed.pycommon import gmNetworkTools
29
30 from Gnumed.business import gmPerson
31 from Gnumed.business import gmATC
32 from Gnumed.business import gmSurgery
33 from Gnumed.business import gmMedication
34 from Gnumed.business import gmForms
35 from Gnumed.business import gmStaff
36
37 from Gnumed.wxpython import gmGuiHelpers
38 from Gnumed.wxpython import gmRegetMixin
39 from Gnumed.wxpython import gmAuthWidgets
40 from Gnumed.wxpython import gmEditArea
41 from Gnumed.wxpython import gmMacro
42 from Gnumed.wxpython import gmCfgWidgets
43 from Gnumed.wxpython import gmListWidgets
44 from Gnumed.wxpython import gmPhraseWheel
45 from Gnumed.wxpython import gmFormWidgets
46 from Gnumed.wxpython import gmAllergyWidgets
47
48
49 _log = logging.getLogger('gm.ui')
50
51
52
53
71
73 dbcfg = gmCfg.cCfgSQL()
74
75
76 default_db = dbcfg.get2 (
77 option = 'external.drug_data.default_source',
78 workplace = gmSurgery.gmCurrentPractice().active_workplace,
79 bias = 'workplace'
80 )
81
82
83 if default_db is None:
84 gmDispatcher.send('statustext', msg = _('No default drug database configured.'), beep = True)
85 configure_drug_data_source(parent = parent)
86 default_db = dbcfg.get2 (
87 option = 'external.drug_data.default_source',
88 workplace = gmSurgery.gmCurrentPractice().active_workplace,
89 bias = 'workplace'
90 )
91
92 if default_db is None:
93 gmGuiHelpers.gm_show_error (
94 aMessage = _('There is no default drug database configured.'),
95 aTitle = _('Jumping to drug database')
96 )
97 return None
98
99
100
101 try:
102 drug_db = gmMedication.drug_data_source_interfaces[default_db]()
103 except KeyError:
104
105 _log.error('faulty default drug data source configuration: %s', default_db)
106
107 configure_drug_data_source(parent = parent)
108 default_db = dbcfg.get2 (
109 option = 'external.drug_data.default_source',
110 workplace = gmSurgery.gmCurrentPractice().active_workplace,
111 bias = 'workplace'
112 )
113
114 try:
115 drug_db = gmMedication.drug_data_source_interfaces[default_db]()
116 except KeyError:
117 _log.error('still faulty default drug data source configuration: %s', default_db)
118 return None
119
120 pat = gmPerson.gmCurrentPatient()
121 if pat.connected:
122 drug_db.patient = pat
123
124 return drug_db
125
132
133
135
136 dbcfg = gmCfg.cCfgSQL()
137
138 ifap_cmd = dbcfg.get2 (
139 option = 'external.ifap-win.shell_command',
140 workplace = gmSurgery.gmCurrentPractice().active_workplace,
141 bias = 'workplace',
142 default = 'wine "C:\Ifapwin\WIAMDB.EXE"'
143 )
144 found, binary = gmShellAPI.detect_external_binary(ifap_cmd)
145 if not found:
146 gmDispatcher.send('statustext', msg = _('Cannot call IFAP via [%s].') % ifap_cmd)
147 return False
148 ifap_cmd = binary
149
150 if import_drugs:
151 transfer_file = os.path.expanduser(dbcfg.get2 (
152 option = 'external.ifap-win.transfer_file',
153 workplace = gmSurgery.gmCurrentPractice().active_workplace,
154 bias = 'workplace',
155 default = '~/.wine/drive_c/Ifapwin/ifap2gnumed.csv'
156 ))
157
158 try:
159 f = open(transfer_file, 'w+b').close()
160 except IOError:
161 _log.exception('Cannot create IFAP <-> GNUmed transfer file [%s]', transfer_file)
162 gmDispatcher.send('statustext', msg = _('Cannot create IFAP <-> GNUmed transfer file [%s].') % transfer_file)
163 return False
164
165 wx.BeginBusyCursor()
166 gmShellAPI.run_command_in_shell(command = ifap_cmd, blocking = import_drugs)
167 wx.EndBusyCursor()
168
169 if import_drugs:
170
171
172 try:
173 csv_file = open(transfer_file, 'rb')
174 except:
175 _log.exception('cannot access [%s]', fname)
176 csv_file = None
177
178 if csv_file is not None:
179 import csv
180 csv_lines = csv.DictReader (
181 csv_file,
182 fieldnames = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split(),
183 delimiter = ';'
184 )
185 pat = gmPerson.gmCurrentPatient()
186 emr = pat.get_emr()
187
188 epi = emr.add_episode(episode_name = _('Current medication'))
189 for line in csv_lines:
190 narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % (
191 line['Packungszahl'].strip(),
192 line['Handelsname'].strip(),
193 line['Form'].strip(),
194 line[u'Packungsgr\xf6\xdfe'].strip(),
195 line['Abpackungsmenge'].strip(),
196 line['Einheit'].strip(),
197 line['Hersteller'].strip(),
198 line['PZN'].strip()
199 )
200 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
201 csv_file.close()
202
203 return True
204
205
206
207
208
210
211 if parent is None:
212 parent = wx.GetApp().GetTopWindow()
213
214 def refresh(lctrl):
215 atcs = gmATC.get_reference_atcs()
216
217 items = [ [
218 a['atc'],
219 a['term'],
220 u'%s' % gmTools.coalesce(a['ddd'], u''),
221 gmTools.coalesce(a['unit'], u''),
222 gmTools.coalesce(a['administrative_route'], u''),
223 gmTools.coalesce(a['comment'], u''),
224 a['version'],
225 a['lang']
226 ] for a in atcs ]
227 lctrl.set_string_items(items)
228 lctrl.set_data(atcs)
229
230 gmListWidgets.get_choices_from_list (
231 parent = parent,
232 msg = _('\nThe ATC codes as known to GNUmed.\n'),
233 caption = _('Showing ATC codes.'),
234 columns = [ u'ATC', _('Term'), u'DDD', _('Unit'), _(u'Route'), _('Comment'), _('Version'), _('Language') ],
235 single_selection = True,
236 refresh_callback = refresh
237 )
238
239
241
242 dlg = wx.FileDialog (
243 parent = None,
244 message = _('Choose an ATC import config file'),
245 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
246 defaultFile = '',
247 wildcard = "%s (*.conf)|*.conf|%s (*)|*" % (_('config files'), _('all files')),
248 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
249 )
250
251 result = dlg.ShowModal()
252 if result == wx.ID_CANCEL:
253 return
254
255 cfg_file = dlg.GetPath()
256 dlg.Destroy()
257
258 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing ATC reference data'))
259 if conn is None:
260 return False
261
262 wx.BeginBusyCursor()
263
264 if gmATC.atc_import(cfg_fname = cfg_file, conn = conn):
265 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported ATC reference data.'))
266 else:
267 gmDispatcher.send(signal = 'statustext', msg = _('Importing ATC reference data failed.'), beep = True)
268
269 wx.EndBusyCursor()
270 return True
271
272
273
275
277
278 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
279
280 query = u"""
281
282 SELECT DISTINCT ON (label)
283 atc_code,
284 label
285 FROM (
286
287 SELECT
288 code as atc_code,
289 (code || ': ' || term || coalesce(' (' || ddd || unit || ')', ''))
290 AS label
291 FROM ref.atc
292 WHERE
293 term %(fragment_condition)s
294 OR
295 code %(fragment_condition)s
296
297 UNION ALL
298
299 SELECT
300 atc_code,
301 (atc_code || ': ' || description)
302 AS label
303 FROM ref.consumable_substance
304 WHERE
305 description %(fragment_condition)s
306 OR
307 atc_code %(fragment_condition)s
308
309 UNION ALL
310
311 SELECT
312 atc_code,
313 (atc_code || ': ' || description || ' (' || preparation || ')')
314 AS label
315 FROM ref.branded_drug
316 WHERE
317 description %(fragment_condition)s
318 OR
319 atc_code %(fragment_condition)s
320
321 -- it would be nice to be able to include clin.vacc_indication but that's hard to do in SQL
322
323 ) AS candidates
324 WHERE atc_code IS NOT NULL
325 ORDER BY label
326 LIMIT 50"""
327
328 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
329 mp.setThresholds(1, 2, 4)
330
331 self.SetToolTipString(_('Select an ATC (Anatomical-Therapeutic-Chemical) code.'))
332 self.matcher = mp
333 self.selection_only = True
334
335
336
337
339
340 if parent is None:
341 parent = wx.GetApp().GetTopWindow()
342
343 def add_from_db(substance):
344 drug_db = get_drug_database(parent = parent)
345 if drug_db is None:
346 return False
347 drug_db.import_drugs()
348 return True
349
350 def edit(substance=None):
351 return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None))
352
353 def delete(substance):
354 if substance.is_in_use_by_patients:
355 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True)
356 return False
357
358 return gmMedication.delete_consumable_substance(substance = substance['pk'])
359
360 def refresh(lctrl):
361 substs = gmMedication.get_consumable_substances(order_by = 'description')
362 items = [ [
363 s['description'],
364 s['amount'],
365 s['unit'],
366 gmTools.coalesce(s['atc_code'], u''),
367 s['pk']
368 ] for s in substs ]
369 lctrl.set_string_items(items)
370 lctrl.set_data(substs)
371
372 msg = _('\nThese are the consumable substances registered with GNUmed.\n')
373
374 gmListWidgets.get_choices_from_list (
375 parent = parent,
376 msg = msg,
377 caption = _('Showing consumable substances.'),
378 columns = [_('Substance'), _('Amount'), _('Unit'), 'ATC', u'#'],
379 single_selection = True,
380 new_callback = edit,
381 edit_callback = edit,
382 delete_callback = delete,
383 refresh_callback = refresh,
384 left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db)
385 )
386
387
389
390 if substance is not None:
391 if substance.is_in_use_by_patients:
392 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit this substance. It is in use.'), beep = True)
393 return False
394
395 ea = cConsumableSubstanceEAPnl(parent = parent, id = -1)
396 ea.data = substance
397 ea.mode = gmTools.coalesce(substance, 'new', 'edit')
398 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
399 dlg.SetTitle(gmTools.coalesce(substance, _('Adding new consumable substance'), _('Editing consumable substance')))
400 if dlg.ShowModal() == wx.ID_OK:
401 dlg.Destroy()
402 return True
403 dlg.Destroy()
404 return False
405
406
407 from Gnumed.wxGladeWidgets import wxgConsumableSubstanceEAPnl
408
410
428
429
430
431
432
433
434
435
437
438 validity = True
439
440 if self._TCTRL_substance.GetValue().strip() == u'':
441 validity = False
442 self.display_tctrl_as_valid(tctrl = self._TCTRL_substance, valid = False)
443 self._TCTRL_substance.SetFocus()
444 else:
445 self.display_tctrl_as_valid(tctrl = self._TCTRL_substance, valid = True)
446
447 try:
448 decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.'))
449 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
450 except (TypeError, decimal.InvalidOperation):
451 validity = False
452 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
453 self._TCTRL_amount.SetFocus()
454
455 if self._PRW_unit.GetValue().strip() == u'':
456 validity = False
457 self._PRW_unit.display_as_valid(valid = False)
458 self._TCTRL_substance.SetFocus()
459 else:
460 self._PRW_unit.display_as_valid(valid = True)
461
462 if validity is False:
463 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. Missing essential input.'))
464
465 return validity
466
468 subst = gmMedication.create_consumable_substance (
469 substance = self._TCTRL_substance.GetValue().strip(),
470 atc = self._PRW_atc.GetData(),
471 amount = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.')),
472 unit = gmTools.coalesce(self._PRW_unit.GetData(), self._PRW_unit.GetValue().strip(), function_initial = ('strip', None))
473 )
474 success, data = subst.save()
475 if not success:
476 err, msg = data
477 _log.error(err)
478 _log.error(msg)
479 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. %s') % msg, beep = True)
480 return False
481
482 self.data = subst
483 return True
484
486 self.data['description'] = self._TCTRL_substance.GetValue().strip()
487 self.data['atc_code'] = self._PRW_atc.GetData()
488 self.data['amount'] = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', '.'))
489 self.data['unit'] = gmTools.coalesce(self._PRW_unit.GetData(), self._PRW_unit.GetValue().strip(), function_initial = ('strip', None))
490 success, data = self.data.save()
491
492 if not success:
493 err, msg = data
494 _log.error(err)
495 _log.error(msg)
496 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save consumable substance. %s') % msg, beep = True)
497 return False
498
499 return True
500
502 self._TCTRL_substance.SetValue(u'')
503 self._TCTRL_amount.SetValue(u'')
504 self._PRW_unit.SetText(u'', None)
505 self._PRW_atc.SetText(u'', None)
506
507 self._TCTRL_substance.SetFocus()
508
516
518 self._refresh_as_new()
519
520
521
522
532
533 def delete(component):
534 if component.is_in_use_by_patients:
535 gmDispatcher.send(signal = 'statustext', msg = _('Cannot remove this component from the drug. It is in use.'), beep = True)
536 return False
537
538 return component.containing_drug.remove_component(substance = component['pk_component'])
539
540 def refresh(lctrl):
541 comps = gmMedication.get_drug_components()
542 items = [ [
543 u'%s%s' % (c['brand'], gmTools.coalesce(c['atc_brand'], u'', u' [%s]')),
544 u'%s%s' % (c['substance'], gmTools.coalesce(c['atc_substance'], u'', u' [%s]')),
545 u'%s %s' % (c['amount'], c['unit']),
546 c['preparation'],
547 gmTools.coalesce(c['external_code_brand'], u'', u'%%s [%s]' % c['external_code_type_brand']),
548 c['pk_component']
549 ] for c in comps ]
550 lctrl.set_string_items(items)
551 lctrl.set_data(comps)
552
553 msg = _('\nThese are the components in the drug brands known to GNUmed.\n')
554
555 gmListWidgets.get_choices_from_list (
556 parent = parent,
557 msg = msg,
558 caption = _('Showing drug brand components.'),
559 columns = [_('Brand'), _('Substance'), _('Strength'), _('Preparation'), _('Code'), u'#'],
560 single_selection = True,
561
562 edit_callback = edit,
563 delete_callback = delete,
564 refresh_callback = refresh
565 )
566
567
579
580
581 from Gnumed.wxGladeWidgets import wxgDrugComponentEAPnl
582
583 -class cDrugComponentEAPnl(wxgDrugComponentEAPnl.wxgDrugComponentEAPnl, gmEditArea.cGenericEditAreaMixin):
584
602
603
604
605
606
607
608
609
611 if self.data is not None:
612 if self.data['is_in_use']:
613 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit drug component. It is in use.'), beep = True)
614 return False
615
616 validity = True
617
618 if self._PRW_substance.GetData() is None:
619 validity = False
620 self._PRW_substance.display_as_valid(False)
621 else:
622 self._PRW_substance.display_as_valid(True)
623
624 val = self._TCTRL_amount.GetValue().strip().replace(',', u'.', 1)
625 try:
626 decimal.Decimal(val)
627 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
628 except:
629 validity = False
630 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
631
632 if self._PRW_unit.GetValue().strip() == u'':
633 validity = False
634 self._PRW_unit.display_as_valid(False)
635 else:
636 self._PRW_unit.display_as_valid(True)
637
638 if validity is False:
639 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save drug component. Invalid or missing essential input.'))
640
641 return validity
642
644
645 data = 1
646 data[''] = 1
647 data[''] = 1
648
649
650
651
652
653
654 return False
655 return True
656
658 self.data['pk_consumable_substance'] = self._PRW_substance.GetData()
659 self.data['amount'] = decimal.Decimal(self._TCTRL_amount.GetValue().strip().replace(',', u'.', 1))
660 self.data['unit'] = self._PRW_unit.GetValue().strip()
661 return self.data.save()
662
672
674 self._TCTRL_brand.SetValue(u'%s (%s)' % (self.data['brand'], self.data['preparation']))
675 self._TCTRL_components.SetValue(u' / '.join(self.data.containing_drug['components']))
676 details = []
677 if self.data['atc_brand'] is not None:
678 details.append(u'ATC: %s' % self.data['atc_brand'])
679 if self.data['external_code_brand'] is not None:
680 details.append(u'%s: %s' % (self.data['external_code_type_brand'], self.data['external_code_brand']))
681 self._TCTRL_codes.SetValue(u'; '.join(details))
682
683 self._PRW_substance.SetText(self.data['substance'], self.data['pk_consumable_substance'])
684 self._TCTRL_amount.SetValue(u'%s' % self.data['amount'])
685 self._PRW_unit.SetText(self.data['unit'], self.data['unit'])
686
687 self._PRW_substance.SetFocus()
688
690
691
692
693 self._PRW_substance.SetText(u'', None)
694 self._TCTRL_amount.SetValue(u'')
695 self._PRW_unit.SetText(u'', None)
696
697 self._PRW_substance.SetFocus()
698
699
713
714
716
718
719 query = u"""
720 (
721 SELECT DISTINCT ON (preparation)
722 preparation as prep, preparation
723 FROM ref.branded_drug
724 WHERE preparation %(fragment_condition)s
725 ) UNION (
726 SELECT DISTINCT ON (preparation)
727 preparation as prep, preparation
728 FROM clin.substance_intake
729 WHERE preparation %(fragment_condition)s
730 )
731 ORDER BY prep
732 limit 30"""
733
734 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
735 mp.setThresholds(1, 2, 4)
736 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
737 self.SetToolTipString(_('The preparation (form) of the substance or brand.'))
738 self.matcher = mp
739 self.selection_only = False
740
756
757
758
760
761 if brand is not None:
762 if brand.is_in_use_by_patients:
763 gmGuiHelpers.gm_show_info (
764 aTitle = _('Managing components of a drug'),
765 aMessage = _(
766 'Cannot manage the components of the branded drug product\n'
767 '\n'
768 ' "%s" (%s)\n'
769 '\n'
770 'because it is currently taken by patients.\n'
771 ) % (brand['brand'], brand['preparation'])
772 )
773 return False
774
775 if parent is None:
776 parent = wx.GetApp().GetTopWindow()
777
778
779
780
781 if brand is None:
782 msg = _('Pick the substances which are components of this drug.')
783 right_col = _('Components of drug')
784 comp_substs = []
785 else:
786 right_col = u'%s (%s)' % (brand['brand'], brand['preparation'])
787 msg = _(
788 'Adjust the components of "%s"\n'
789 '\n'
790 'The drug must contain at least one component. Any given\n'
791 'substance can only be included once per drug.'
792 ) % right_col
793 comp_substs = [ c.substance for c in brand.components ]
794
795 substs = gmMedication.get_consumable_substances(order_by = 'description')
796 choices = [ u'%s %s %s' % (s['description'], s['amount'], s['unit']) for s in substs ]
797 picks = [ u'%s %s %s' % (c['description'], c['amount'], c['unit']) for c in comp_substs ]
798
799 picker = gmListWidgets.cItemPickerDlg (
800 parent,
801 -1,
802 title = _('Managing components of a drug ...'),
803 msg = msg
804 )
805 picker.set_columns(['Substances'], [right_col])
806 picker.set_choices(choices = choices, data = substs)
807 picker.set_picks(picks = picks, data = comp_substs)
808
809
810
811
812
813
814 btn_pressed = picker.ShowModal()
815 substs = picker.get_picks()
816 picker.Destroy()
817
818 if btn_pressed != wx.ID_OK:
819 return (False, None)
820
821 if brand is not None:
822 brand.set_substances_as_components(substances = substs)
823
824 return (True, substs)
825
827
828 if parent is None:
829 parent = wx.GetApp().GetTopWindow()
830
831 def add_from_db(brand):
832 drug_db = get_drug_database(parent = parent)
833 if drug_db is None:
834 return False
835 drug_db.import_drugs()
836 return True
837
838 def get_tooltip(brand=None):
839 tt = u'%s %s\n' % (brand['brand'], brand['preparation'])
840 tt += u'\n'
841 tt += u'%s%s%s\n' % (
842 gmTools.bool2subst(brand.is_vaccine, u'%s, ' % _('Vaccine'), u''),
843 u'%s, ' % gmTools.bool2subst(brand.is_in_use_by_patients, _('in use'), _('not in use')),
844 gmTools.bool2subst(brand['is_fake_brand'], _('fake'), u'')
845 )
846 tt += gmTools.coalesce(brand['atc'], u'', _('ATC: %s\n'))
847 tt += gmTools.coalesce(brand['external_code'], u'', u'%s: %%s\n' % brand['external_code_type'])
848 if brand['components'] is not None:
849 tt += u'- %s' % u'\n- '.join(brand['components'])
850 return tt
851
852 def edit(brand):
853 if brand is not None:
854 if brand.is_vaccine:
855 gmGuiHelpers.gm_show_info (
856 aTitle = _('Editing medication'),
857 aMessage = _(
858 'Cannot edit the medication\n'
859 '\n'
860 ' "%s" (%s)\n'
861 '\n'
862 'because it is a vaccine. Please edit it\n'
863 'from the vaccine management section !\n'
864 ) % (brand['brand'], brand['preparation'])
865 )
866 return False
867
868 return edit_branded_drug(parent = parent, branded_drug = brand, single_entry = True)
869
870 def delete(brand):
871 if brand.is_vaccine:
872 gmGuiHelpers.gm_show_info (
873 aTitle = _('Deleting medication'),
874 aMessage = _(
875 'Cannot delete the medication\n'
876 '\n'
877 ' "%s" (%s)\n'
878 '\n'
879 'because it is a vaccine. Please delete it\n'
880 'from the vaccine management section !\n'
881 ) % (brand['brand'], brand['preparation'])
882 )
883 return False
884 gmMedication.delete_branded_drug(brand = brand['pk_brand'])
885 return True
886
887 def new():
888 return edit_branded_drug(parent = parent, branded_drug = None, single_entry = False)
889
890 def refresh(lctrl):
891 drugs = gmMedication.get_branded_drugs()
892 items = [ [
893 u'%s%s' % (
894 d['brand'],
895 gmTools.bool2subst(d['is_fake_brand'], ' (%s)' % _('fake'), u'')
896 ),
897 d['preparation'],
898 gmTools.coalesce(d['atc'], u''),
899 gmTools.coalesce(d['components'], u''),
900 gmTools.coalesce(d['external_code'], u'', u'%%s [%s]' % d['external_code_type']),
901 d['pk_brand']
902 ] for d in drugs ]
903 lctrl.set_string_items(items)
904 lctrl.set_data(drugs)
905
906 msg = _('\nThese are the drug brands known to GNUmed.\n')
907
908 gmListWidgets.get_choices_from_list (
909 parent = parent,
910 msg = msg,
911 caption = _('Showing branded drugs.'),
912 columns = [_('Name'), _('Preparation'), _('ATC'), _('Components'), _('Code'), u'#'],
913 single_selection = True,
914 ignore_OK_button = ignore_OK_button,
915 refresh_callback = refresh,
916 new_callback = new,
917 edit_callback = edit,
918 delete_callback = delete,
919 list_tooltip_callback = get_tooltip,
920 left_extra_button = (_('Import'), _('Import substances and brands from a drug database.'), add_from_db)
921
922
923 )
924
925
927
928 if branded_drug is not None:
929 if branded_drug.is_in_use_by_patients:
930 gmGuiHelpers.gm_show_info (
931 aTitle = _('Editing drug'),
932 aMessage = _(
933 'Cannot edit the branded drug product\n'
934 '\n'
935 ' "%s" (%s)\n'
936 '\n'
937 'because it is currently taken by patients.\n'
938 ) % (branded_drug['brand'], branded_drug['preparation'])
939 )
940 return False
941
942 if parent is None:
943 parent = wx.GetApp().GetTopWindow()
944
945 def manage_substances(drug):
946 manage_consumable_substances(parent = parent)
947
948 ea = cBrandedDrugEAPnl(parent = parent, id = -1)
949 ea.data = branded_drug
950 ea.mode = gmTools.coalesce(branded_drug, 'new', 'edit')
951 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
952 dlg.SetTitle(gmTools.coalesce(branded_drug, _('Adding new drug brand'), _('Editing drug brand')))
953 dlg.left_extra_button = (
954 _('Substances'),
955 _('Manage consumable substances'),
956 manage_substances
957 )
958 if dlg.ShowModal() == wx.ID_OK:
959 dlg.Destroy()
960 return True
961 dlg.Destroy()
962 return False
963
964
965 from Gnumed.wxGladeWidgets import wxgBrandedDrugEAPnl
966
967 -class cBrandedDrugEAPnl(wxgBrandedDrugEAPnl.wxgBrandedDrugEAPnl, gmEditArea.cGenericEditAreaMixin):
968
985
986
987
988
989
990
991
992
994
995 if self.data is not None:
996 if self.data.is_in_use_by_patients:
997 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit drug brand. It is in use.'), beep = True)
998 return False
999
1000 validity = True
1001
1002 if self._PRW_brand.GetValue().strip() == u'':
1003 validity = False
1004 self._PRW_brand.display_as_valid(False)
1005 else:
1006 self._PRW_brand.display_as_valid(True)
1007
1008 if self._PRW_preparation.GetValue().strip() == u'':
1009 validity = False
1010 self._PRW_preparation.display_as_valid(False)
1011 else:
1012 self._PRW_preparation.display_as_valid(True)
1013
1014 if validity is True:
1015 self._TCTRL_components.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND))
1016 if len(self.__component_substances) == 0:
1017 wants_empty = gmGuiHelpers.gm_show_question (
1018 title = _('Checking brand data'),
1019 question = _(
1020 'You have not selected any substances\n'
1021 'as drug components.\n'
1022 '\n'
1023 'Without components you will not be able to\n'
1024 'use this drug for documenting patient care.\n'
1025 '\n'
1026 'Are you sure you want to save\n'
1027 'it without components ?'
1028 )
1029 )
1030 if not wants_empty:
1031 validity = False
1032 self.display_ctrl_as_valid(ctrl = self._TCTRL_components, valid = False)
1033
1034 if validity is False:
1035 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save branded drug. Invalid or missing essential input.'))
1036
1037 return validity
1038
1040
1041 drug = gmMedication.create_branded_drug (
1042 brand_name = self._PRW_brand.GetValue().strip(),
1043 preparation = gmTools.coalesce (
1044 self._PRW_preparation.GetData(),
1045 self._PRW_preparation.GetValue()
1046 ).strip(),
1047 return_existing = True
1048 )
1049 drug['is_fake_brand'] = self._CHBOX_is_fake.GetValue()
1050 drug['atc'] = self._PRW_atc.GetData()
1051 code = self._TCTRL_external_code.GetValue().strip()
1052 if code != u'':
1053 drug['external_code'] = code
1054 drug['external_code_type'] = self._PRW_external_code_type.GetData().strip()
1055
1056 drug.save()
1057
1058 if len(self.__component_substances) > 0:
1059 drug.set_substances_as_components(substances = self.__component_substances)
1060
1061 self.data = drug
1062
1063 return True
1064
1066 self.data['brand'] = self._PRW_brand.GetValue().strip()
1067 self.data['preparation'] = gmTools.coalesce (
1068 self._PRW_preparation.GetData(),
1069 self._PRW_preparation.GetValue()
1070 ).strip()
1071 self.data['is_fake_brand'] = self._CHBOX_is_fake.GetValue()
1072 self.data['atc'] = self._PRW_atc.GetData()
1073 code = self._TCTRL_external_code.GetValue().strip()
1074 if code != u'':
1075 self.data['external_code'] = code
1076 self.data['external_code_type'] = self._PRW_external_code_type.GetData().strip()
1077 success, data = self.data.save()
1078 if not success:
1079 err, msg = data
1080 _log.error('problem saving')
1081 _log.error('%s', err)
1082 _log.error('%s', msg)
1083 return (success is True)
1084
1086 self._PRW_brand.SetText(u'', None)
1087 self._PRW_preparation.SetText(u'', None)
1088 self._CHBOX_is_fake.SetValue(False)
1089 self._TCTRL_components.SetValue(u'')
1090 self._PRW_atc.SetText(u'', None)
1091 self._TCTRL_external_code.SetValue(u'')
1092 self._PRW_external_code_type.SetText(u'', None)
1093
1094 self._PRW_brand.SetFocus()
1095
1096 self.__component_substances = []
1097
1099 self._refresh_as_new()
1100
1117
1118
1119
1133
1135
1137
1138 query = u"""
1139 SELECT
1140 pk
1141 AS data,
1142 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
1143 AS list_label,
1144 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
1145 AS field_label
1146 FROM ref.branded_drug
1147 WHERE description %(fragment_condition)s
1148 ORDER BY list_label
1149 LIMIT 50"""
1150
1151 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
1152 mp.setThresholds(2, 3, 4)
1153 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1154 self.SetToolTipString(_(
1155 'The brand name of the drug.\n'
1156 '\n'
1157 'Note: a brand name will need to be linked to\n'
1158 'one or more components before it can be used,\n'
1159 'except in the case of fake (generic) vaccines.'
1160 ))
1161 self.matcher = mp
1162 self.selection_only = False
1163
1164
1165
1166
1168
1170
1171 query = u"""
1172 SELECT DISTINCT ON (sched)
1173 schedule as sched,
1174 schedule
1175 FROM clin.substance_intake
1176 WHERE schedule %(fragment_condition)s
1177 ORDER BY sched
1178 LIMIT 50"""
1179
1180 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
1181 mp.setThresholds(1, 2, 4)
1182 mp.word_separators = '[ \t=+&:@]+'
1183 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1184 self.SetToolTipString(_('The schedule for taking this substance.'))
1185 self.matcher = mp
1186 self.selection_only = False
1187
1189
1190 if intake['is_currently_active']:
1191 intake['discontinued'] = gmDateTime.pydt_now_here()
1192 if intake['discontinue_reason'] is None:
1193 intake['discontinue_reason'] = u'%s %s' % (_('not tolerated:'), _('discontinued due to allergy or intolerance'))
1194 else:
1195 if not intake['discontinue_reason'].startswith(_('not tolerated:')):
1196 intake['discontinue_reason'] = u'%s %s' % (_('not tolerated:'), intake['discontinue_reason'])
1197 if not intake.save():
1198 return False
1199
1200 allg = intake.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter'])
1201
1202 brand = intake.containing_drug
1203 if brand is not None:
1204 comps = [ c['substance'] for c in brand.components ]
1205 if len(comps) > 1:
1206 gmGuiHelpers.gm_show_info (
1207 aTitle = _(u'Documented an allergy'),
1208 aMessage = _(
1209 u'An allergy was documented against the substance:\n'
1210 u'\n'
1211 u' [%s]\n'
1212 u'\n'
1213 u'This substance was taken with the multi-component brand:\n'
1214 u'\n'
1215 u' [%s (%s)]\n'
1216 u'\n'
1217 u'Note that ALL components of this brand were discontinued.'
1218 ) % (
1219 intake['substance'],
1220 intake['brand'],
1221 u' & '.join(comps)
1222 )
1223 )
1224
1225 if parent is None:
1226 parent = wx.GetApp().GetTopWindow()
1227
1228 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent = parent, id = -1)
1229 dlg.ShowModal()
1230
1231 return True
1232
1233 from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl
1234
1235 -class cSubstanceIntakeEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
1236
1254
1256
1257 self._PRW_component.add_callback_on_lose_focus(callback = self._on_leave_component)
1258 self._PRW_component.selection_only = True
1259
1260 self._PRW_substance.add_callback_on_lose_focus(callback = self._on_leave_substance)
1261 self._PRW_substance.selection_only = True
1262
1264 emr = gmPerson.gmCurrentPatient().get_emr()
1265
1266 state = emr.allergy_state
1267 if state['last_confirmed'] is None:
1268 confirmed = _('never')
1269 else:
1270 confirmed = state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
1271 msg = _(u'%s, last confirmed %s\n') % (state.state_string, confirmed)
1272 msg += gmTools.coalesce(state['comment'], u'', _('Comment (%s): %%s\n') % state['modified_by'])
1273 msg += u'\n'
1274
1275 for allergy in emr.get_allergies():
1276 msg += u'%s (%s, %s): %s\n' % (
1277 allergy['descriptor'],
1278 allergy['l10n_type'],
1279 gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), u'?'),
1280 gmTools.coalesce(allergy['reaction'], _('reaction not recorded'))
1281 )
1282
1283 self._LBL_allergies.SetLabel(msg)
1284
1285
1286
1376
1378
1379 emr = gmPerson.gmCurrentPatient().get_emr()
1380 epi = self._PRW_episode.GetData(can_create = True)
1381
1382 if self._PRW_substance.GetData() is None:
1383
1384 intake = emr.add_substance_intake (
1385 pk_component = self._PRW_component.GetData(),
1386 episode = epi
1387 )
1388 else:
1389 intake = emr.add_substance_intake (
1390 pk_substance = self._PRW_substance.GetData(),
1391 episode = epi,
1392 preparation = self._PRW_preparation.GetValue().strip()
1393 )
1394
1395 if intake is None:
1396 gmDispatcher.send('statustext', msg = _('Cannot add duplicate of (maybe inactive) substance intake.'), beep = True)
1397 return False
1398
1399 intake['started'] = self._DP_started.GetData()
1400 intake['discontinued'] = self._DP_discontinued.GetData()
1401 if intake['discontinued'] is None:
1402 intake['discontinue_reason'] = None
1403 else:
1404 intake['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
1405 intake['schedule'] = self._PRW_schedule.GetValue().strip()
1406 intake['aim'] = self._PRW_aim.GetValue().strip()
1407 intake['notes'] = self._PRW_notes.GetValue().strip()
1408 intake['is_long_term'] = self._CHBOX_long_term.IsChecked()
1409 intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
1410 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]:
1411 intake['duration'] = None
1412 else:
1413 intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
1414 intake.save()
1415
1416 self.data = intake
1417
1418 return True
1419
1421
1422
1423 self.data['started'] = self._DP_started.GetData()
1424 self.data['discontinued'] = self._DP_discontinued.GetData()
1425 if self.data['discontinued'] is None:
1426 self.data['discontinue_reason'] = None
1427 else:
1428 self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
1429 self.data['schedule'] = self._PRW_schedule.GetValue()
1430 self.data['is_long_term'] = self._CHBOX_long_term.IsChecked()
1431 self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
1432 if self._PRW_duration.GetValue().strip() in [u'', gmTools.u_infinity]:
1433 self.data['duration'] = None
1434 else:
1435 self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
1436
1437
1438 self.data['preparation'] = self._PRW_preparation.GetValue()
1439
1440
1441 self.data['aim'] = self._PRW_aim.GetValue()
1442 self.data['notes'] = self._PRW_notes.GetValue()
1443 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
1444
1445 self.data.save()
1446
1447 return True
1448
1450 self._PRW_component.SetText(u'', None)
1451 self._LBL_component.Enable(True)
1452 self._PRW_component.Enable(True)
1453 self._TCTRL_brand_ingredients.SetValue(u'')
1454 self._TCTRL_brand_ingredients.SetToolTipString(u'')
1455
1456 self._LBL_or.Enable(True)
1457
1458 self._PRW_substance.SetText(u'', None)
1459 self._PRW_substance.Enable(True)
1460
1461 self._PRW_preparation.SetText(u'', None)
1462 self._PRW_preparation.Enable(True)
1463
1464 self._PRW_schedule.SetText(u'', None)
1465 self._PRW_duration.SetText(u'', None)
1466 self._PRW_aim.SetText(u'', None)
1467 self._PRW_notes.SetText(u'', None)
1468 self._PRW_episode.SetText(u'', None)
1469
1470 self._CHBOX_long_term.SetValue(False)
1471 self._CHBOX_approved.SetValue(True)
1472
1473 self._DP_started.SetData(gmDateTime.pydt_now_here())
1474 self._DP_discontinued.SetData(None)
1475 self._PRW_discontinue_reason.SetValue(u'')
1476
1477 self.__refresh_allergies()
1478
1479 self._PRW_component.SetFocus()
1480
1482
1483 self._TCTRL_brand_ingredients.SetValue(u'')
1484 self._TCTRL_brand_ingredients.SetToolTipString(u'')
1485
1486 if self.data['pk_brand'] is None:
1487 self.__refresh_from_existing_substance()
1488 else:
1489 self.__refresh_from_existing_component()
1490
1491
1492 self._LBL_component.Enable(False)
1493 self._PRW_component.Enable(False)
1494 self._LBL_or.Enable(False)
1495 self._PRW_substance.Enable(False)
1496
1497 if self.data['is_long_term']:
1498 self._CHBOX_long_term.SetValue(True)
1499 self._PRW_duration.Enable(False)
1500 self._PRW_duration.SetText(gmTools.u_infinity, None)
1501 self._BTN_discontinued_as_planned.Enable(False)
1502 else:
1503 self._CHBOX_long_term.SetValue(False)
1504 self._PRW_duration.Enable(True)
1505 self._BTN_discontinued_as_planned.Enable(True)
1506 if self.data['duration'] is None:
1507 self._PRW_duration.SetText(u'', None)
1508 else:
1509 self._PRW_duration.SetText(gmDateTime.format_interval(self.data['duration'], gmDateTime.acc_days), self.data['duration'])
1510 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], u''), self.data['aim'])
1511 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], u''), self.data['notes'])
1512 self._PRW_episode.SetData(self.data['pk_episode'])
1513 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], u''), self.data['schedule'])
1514
1515 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of'])
1516
1517 self._DP_started.SetData(self.data['started'])
1518 self._DP_discontinued.SetData(self.data['discontinued'])
1519 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], u''))
1520 if self.data['discontinued'] is not None:
1521 self._PRW_discontinue_reason.Enable()
1522
1523 self.__refresh_allergies()
1524
1525 self._PRW_schedule.SetFocus()
1526
1528 self._LBL_component.Enable(False)
1529 self._PRW_component.Enable(False)
1530 self._PRW_component.SetText(u'', None)
1531 self._PRW_component.display_as_valid(True)
1532
1533 self._LBL_or.Enable(False)
1534
1535 self._PRW_substance.Enable(True)
1536 self._PRW_substance.SetText (
1537 u'%s %s %s' % (self.data['substance'], self.data['amount'], self.data['unit']),
1538 self.data['pk_substance']
1539 )
1540
1541 self._PRW_preparation.SetText(gmTools.coalesce(self.data['preparation'], u''), self.data['preparation'])
1542 self._PRW_preparation.Enable(True)
1543
1545 self._LBL_component.Enable(True)
1546 self._PRW_component.Enable(True)
1547 self._PRW_component.SetText (
1548 u'%s %s %s (%s)' % (self.data['substance'], self.data['amount'], self.data['unit'], self.data['brand']),
1549 self.data['pk_drug_component']
1550 )
1551
1552 brand = gmMedication.cBrandedDrug(aPK_obj = self.data['pk_brand'])
1553 if brand['components'] is not None:
1554 self._TCTRL_brand_ingredients.SetValue(u'; '.join(brand['components']))
1555 tt = u'%s:\n\n- %s' % (
1556 self.data['brand'],
1557 u'\n- '.join(brand['components'])
1558 )
1559 self._TCTRL_brand_ingredients.SetToolTipString(tt)
1560
1561 self._LBL_or.Enable(False)
1562 self._LBL_substance.Enable(False)
1563 self._PRW_substance.SetText(u'', None)
1564 self._PRW_substance.display_as_valid(True)
1565
1566 self._PRW_preparation.SetText(self.data['preparation'], self.data['preparation'])
1567 self._PRW_preparation.Enable(False)
1568
1570 self._refresh_as_new()
1571
1572
1573
1575 if self._PRW_component.GetData() is None:
1576 self._LBL_or.Enable(True)
1577 self._PRW_component.SetText(u'', None)
1578 self._LBL_substance.Enable(True)
1579 self._PRW_substance.Enable(True)
1580 self._LBL_preparation.Enable(True)
1581 self._PRW_preparation.Enable(True)
1582 self._PRW_preparation.SetText(u'', None)
1583 self._TCTRL_brand_ingredients.SetValue(u'')
1584 self._TCTRL_brand_ingredients.SetToolTipString(u'')
1585 else:
1586 self._LBL_or.Enable(False)
1587 self._LBL_substance.Enable(False)
1588 self._PRW_substance.SetText(u'', None)
1589 self._PRW_substance.display_as_valid(True)
1590 self._PRW_substance.Enable(False)
1591 self._LBL_preparation.Enable(False)
1592 self._PRW_preparation.Enable(False)
1593 comp = gmMedication.cDrugComponent(aPK_obj = self._PRW_component.GetData())
1594 self._PRW_preparation.SetText(comp['preparation'], comp['preparation'])
1595 brand = comp.containing_drug
1596 if brand['components'] is not None:
1597 self._TCTRL_brand_ingredients.SetValue(u'; '.join(brand['components']))
1598 tt = u'%s:\n\n- %s' % (
1599 brand['brand'],
1600 u'\n- '.join(brand['components'])
1601 )
1602 self._TCTRL_brand_ingredients.SetToolTipString(tt)
1603
1605 if self._PRW_substance.GetData() is None:
1606 self._LBL_or.Enable(True)
1607 self._LBL_component.Enable(True)
1608 self._PRW_component.Enable(True)
1609 self._PRW_substance.SetText(u'', None)
1610 else:
1611 self._LBL_or.Enable(False)
1612 self._LBL_component.Enable(False)
1613 self._PRW_component.SetText(u'', None)
1614 self._PRW_component.display_as_valid(True)
1615 self._PRW_component.Enable(False)
1616 self._LBL_preparation.Enable(True)
1617 self._PRW_preparation.Enable(True)
1618 self._TCTRL_brand_ingredients.SetValue(u'')
1619 self._TCTRL_brand_ingredients.SetToolTipString(u'')
1620
1622 if self._DP_discontinued.GetData() is None:
1623 self._PRW_discontinue_reason.Enable(False)
1624 else:
1625 self._PRW_discontinue_reason.Enable(True)
1626
1629
1632
1635
1647
1677
1679 if self._CHBOX_long_term.IsChecked() is True:
1680 self._PRW_duration.Enable(False)
1681 self._BTN_discontinued_as_planned.Enable(False)
1682 self._PRW_discontinue_reason.Enable(False)
1683 else:
1684 self._PRW_duration.Enable(True)
1685 self._BTN_discontinued_as_planned.Enable(True)
1686 self._PRW_discontinue_reason.Enable(True)
1687
1688 self.__refresh_allergies()
1689
1699
1701
1702 subst = gmMedication.cSubstanceIntakeEntry(aPK_obj = substance)
1703 msg = _(
1704 '\n'
1705 '[%s]\n'
1706 '\n'
1707 'It may be prudent to edit (before deletion) the details\n'
1708 'of this substance intake entry so as to leave behind\n'
1709 'some indication of why it was deleted.\n'
1710 ) % subst.format()
1711
1712 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1713 parent,
1714 -1,
1715 caption = _('Deleting medication / substance intake'),
1716 question = msg,
1717 button_defs = [
1718 {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True},
1719 {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')},
1720 {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')}
1721 ]
1722 )
1723
1724 edit_first = dlg.ShowModal()
1725 dlg.Destroy()
1726
1727 if edit_first == wx.ID_CANCEL:
1728 return
1729
1730 if edit_first == wx.ID_YES:
1731 edit_intake_of_substance(parent = parent, substance = subst)
1732 delete_it = gmGuiHelpers.gm_show_question (
1733 aMessage = _('Now delete substance intake entry ?'),
1734 aTitle = _('Deleting medication / substance intake')
1735 )
1736 else:
1737 delete_it = True
1738
1739 if not delete_it:
1740 return
1741
1742 gmMedication.delete_substance_intake(substance = substance)
1743
1758
1759
1760
1761
1789
1791
1792 if parent is None:
1793 parent = wx.GetApp().GetTopWindow()
1794
1795
1796 dbcfg = gmCfg.cCfgSQL()
1797 option = u'form_templates.medication_list'
1798
1799 template = dbcfg.get2 (
1800 option = option,
1801 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1802 bias = 'user'
1803 )
1804
1805 if template is None:
1806 template = configure_medication_list_template(parent = parent)
1807 if template is None:
1808 gmGuiHelpers.gm_show_error (
1809 aMessage = _('There is no medication list template configured.'),
1810 aTitle = _('Printing medication list')
1811 )
1812 return False
1813 else:
1814 try:
1815 name, ver = template.split(u' - ')
1816 except:
1817 _log.exception('problem splitting medication list template name [%s]', template)
1818 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading medication list template.'), beep = True)
1819 return False
1820 template = gmForms.get_form_template(name_long = name, external_version = ver)
1821 if template is None:
1822 gmGuiHelpers.gm_show_error (
1823 aMessage = _('Cannot load medication list template [%s - %s]') % (name, ver),
1824 aTitle = _('Printing medication list')
1825 )
1826 return False
1827
1828
1829 try:
1830 meds_list = template.instantiate()
1831 except KeyError:
1832 _log.exception('cannot instantiate medication list template [%s]', template)
1833 gmGuiHelpers.gm_show_error (
1834 aMessage = _('Invalid medication list template [%s - %s (%s)]') % (name, ver, template['engine']),
1835 aTitle = _('Printing medication list')
1836 )
1837 return False
1838
1839 ph = gmMacro.gmPlaceholderHandler()
1840
1841 meds_list.substitute_placeholders(data_source = ph)
1842 pdf_name = meds_list.generate_output()
1843 if pdf_name is None:
1844 gmGuiHelpers.gm_show_error (
1845 aMessage = _('Error generating the medication list.'),
1846 aTitle = _('Printing medication list')
1847 )
1848 return False
1849
1850
1851 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'medication_list')
1852 if not printed:
1853 gmGuiHelpers.gm_show_error (
1854 aMessage = _('Error printing the medication list.'),
1855 aTitle = _('Printing medication list')
1856 )
1857 return False
1858
1859 pat = gmPerson.gmCurrentPatient()
1860 emr = pat.get_emr()
1861 epi = emr.add_episode(episode_name = 'administration', is_open = False)
1862 emr.add_clin_narrative (
1863 soap_cat = None,
1864 note = _('medication list printed from template [%s - %s]') % (template['name_long'], template['external_version']),
1865 episode = epi
1866 )
1867
1868 return True
1869
1871
1872 if len(prescribed_drugs) == 0:
1873 return
1874
1875 curr_brands = [ i['pk_brand'] for i in emr.get_current_substance_intake() if i['pk_brand'] is not None ]
1876 new_drugs = []
1877 for drug in prescribed_drugs:
1878 if drug['pk_brand'] not in curr_brands:
1879 new_drugs.append(drug)
1880
1881 if len(new_drugs) == 0:
1882 return
1883
1884 if parent is None:
1885 parent = wx.GetApp().GetTopWindow()
1886
1887 dlg = gmListWidgets.cItemPickerDlg (
1888 parent,
1889 -1,
1890 msg = _(
1891 'These brands have been prescribed but are not listed\n'
1892 'in the current medication list of this patient.\n'
1893 '\n'
1894 'Please select those you want added to the medication list.'
1895 )
1896 )
1897 dlg.set_columns (
1898 columns = [_('Newly prescribed drugs')],
1899 columns_right = [_('Add to medication list')]
1900 )
1901 choices = [ (u'%s %s (%s)' % (d['brand'], d['preparation'], u'; '.join(d['components']))) for d in new_drugs ]
1902 dlg.set_choices (
1903 choices = choices,
1904 data = new_drugs
1905 )
1906 dlg.ShowModal()
1907 drugs2add = dlg.get_picks()
1908 dlg.Destroy()
1909
1910 if drugs2add is None:
1911 return
1912
1913 if len(drugs2add) == 0:
1914 return
1915
1916 for drug in drugs2add:
1917
1918 intake = emr.add_substance_intake (
1919 pk_component = drug['pk_components'][0],
1920 episode = emr.add_episode(episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE)['pk_episode'],
1921 )
1922 if intake is None:
1923 continue
1924 intake['intake_is_approved_of'] = True
1925 intake.save()
1926
1927 return
1928
1930 """A grid class for displaying current substance intake.
1931
1932 - does NOT listen to the currently active patient
1933 - thereby it can display any patient at any time
1934 """
1936
1937 wx.grid.Grid.__init__(self, *args, **kwargs)
1938
1939 self.__patient = None
1940 self.__row_data = {}
1941 self.__prev_row = None
1942 self.__prev_tooltip_row = None
1943 self.__prev_cell_0 = None
1944 self.__grouping_mode = u'issue'
1945 self.__filter_show_unapproved = True
1946 self.__filter_show_inactive = True
1947
1948 self.__grouping2col_labels = {
1949 u'issue': [
1950 _('Health issue'),
1951 _('Substance'),
1952 _('Strength'),
1953 _('Schedule'),
1954 _('Started'),
1955 _('Duration / Until'),
1956 _('Brand'),
1957 _('Advice')
1958 ],
1959 u'brand': [
1960 _('Brand'),
1961 _('Schedule'),
1962 _('Substance'),
1963 _('Strength'),
1964 _('Started'),
1965 _('Duration / Until'),
1966 _('Health issue'),
1967 _('Advice')
1968 ],
1969 u'episode': [
1970 _('Episode'),
1971 _('Substance'),
1972 _('Strength'),
1973 _('Schedule'),
1974 _('Started'),
1975 _('Duration / Until'),
1976 _('Brand'),
1977 _('Advice')
1978 ]
1979 }
1980
1981 self.__grouping2order_by_clauses = {
1982 u'issue': u'pk_health_issue nulls first, substance, started',
1983 u'episode': u'pk_health_issue nulls first, episode, substance, started',
1984 u'brand': u'brand nulls last, substance, started'
1985 }
1986
1987 self.__init_ui()
1988 self.__register_events()
1989
1990
1991
1993
1994 sel_block_top_left = self.GetSelectionBlockTopLeft()
1995 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1996 sel_cols = self.GetSelectedCols()
1997 sel_rows = self.GetSelectedRows()
1998
1999 selected_cells = []
2000
2001
2002 selected_cells += self.GetSelectedCells()
2003
2004
2005 selected_cells += list (
2006 (row, col)
2007 for row in sel_rows
2008 for col in xrange(self.GetNumberCols())
2009 )
2010
2011
2012 selected_cells += list (
2013 (row, col)
2014 for row in xrange(self.GetNumberRows())
2015 for col in sel_cols
2016 )
2017
2018
2019 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
2020 selected_cells += [
2021 (row, col)
2022 for row in xrange(top_left[0], bottom_right[0] + 1)
2023 for col in xrange(top_left[1], bottom_right[1] + 1)
2024 ]
2025
2026 return set(selected_cells)
2027
2029 rows = {}
2030
2031 for row, col in self.get_selected_cells():
2032 rows[row] = True
2033
2034 return rows.keys()
2035
2038
2040
2041 self.empty_grid()
2042
2043 if self.__patient is None:
2044 return
2045
2046 emr = self.__patient.get_emr()
2047 meds = emr.get_current_substance_intake (
2048 order_by = self.__grouping2order_by_clauses[self.__grouping_mode],
2049 include_unapproved = self.__filter_show_unapproved,
2050 include_inactive = self.__filter_show_inactive
2051 )
2052 if not meds:
2053 return
2054
2055 self.BeginBatch()
2056
2057
2058 labels = self.__grouping2col_labels[self.__grouping_mode]
2059 if self.__filter_show_unapproved:
2060 self.AppendCols(numCols = len(labels) + 1)
2061 else:
2062 self.AppendCols(numCols = len(labels))
2063 for col_idx in range(len(labels)):
2064 self.SetColLabelValue(col_idx, labels[col_idx])
2065 if self.__filter_show_unapproved:
2066 self.SetColLabelValue(len(labels), u'OK?')
2067 self.SetColSize(len(labels), 40)
2068
2069 self.AppendRows(numRows = len(meds))
2070
2071
2072 for row_idx in range(len(meds)):
2073 med = meds[row_idx]
2074 self.__row_data[row_idx] = med
2075
2076 if med['is_currently_active'] is True:
2077 atcs = []
2078 if med['atc_substance'] is not None:
2079 atcs.append(med['atc_substance'])
2080
2081
2082
2083 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],))
2084 if allg not in [None, False]:
2085 attr = self.GetOrCreateCellAttr(row_idx, 0)
2086 if allg['type'] == u'allergy':
2087 attr.SetTextColour('red')
2088 else:
2089 attr.SetTextColour('yellow')
2090 self.SetRowAttr(row_idx, attr)
2091 else:
2092 attr = self.GetOrCreateCellAttr(row_idx, 0)
2093 attr.SetTextColour('grey')
2094 self.SetRowAttr(row_idx, attr)
2095
2096 if self.__grouping_mode == u'episode':
2097 if med['pk_episode'] is None:
2098 self.__prev_cell_0 = None
2099 epi = gmTools.u_diameter
2100 else:
2101 if self.__prev_cell_0 == med['episode']:
2102 epi = u''
2103 else:
2104 self.__prev_cell_0 = med['episode']
2105 epi = gmTools.coalesce(med['episode'], u'')
2106 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40))
2107
2108 self.SetCellValue(row_idx, 1, med['substance'])
2109 self.SetCellValue(row_idx, 2, u'%s %s' % (med['amount'], med['unit']))
2110 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u''))
2111 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d'))
2112
2113 if med['is_long_term']:
2114 self.SetCellValue(row_idx, 5, gmTools.u_infinity)
2115 else:
2116 if med['discontinued'] is None:
2117 if med['duration'] is None:
2118 self.SetCellValue(row_idx, 5, u'')
2119 else:
2120 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
2121 else:
2122 self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d'))
2123
2124 if med['pk_brand'] is None:
2125 brand = u''
2126 else:
2127 if med['fake_brand']:
2128 brand = gmTools.coalesce(med['brand'], u'', _('%s (fake)'))
2129 else:
2130 brand = gmTools.coalesce(med['brand'], u'')
2131 self.SetCellValue(row_idx, 6, gmTools.wrap(text = brand, width = 35))
2132
2133 elif self.__grouping_mode == u'issue':
2134 if med['pk_health_issue'] is None:
2135 self.__prev_cell_0 = None
2136 issue = u'%s%s' % (
2137 gmTools.u_diameter,
2138 gmTools.coalesce(med['episode'], u'', u' (%s)')
2139 )
2140 else:
2141 if self.__prev_cell_0 == med['health_issue']:
2142 issue = u''
2143 else:
2144 self.__prev_cell_0 = med['health_issue']
2145 issue = med['health_issue']
2146 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40))
2147
2148 self.SetCellValue(row_idx, 1, med['substance'])
2149 self.SetCellValue(row_idx, 2, u'%s %s' % (med['amount'], med['unit']))
2150 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u''))
2151 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d'))
2152
2153 if med['is_long_term']:
2154 self.SetCellValue(row_idx, 5, gmTools.u_infinity)
2155 else:
2156 if med['discontinued'] is None:
2157 if med['duration'] is None:
2158 self.SetCellValue(row_idx, 5, u'')
2159 else:
2160 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
2161 else:
2162 self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d'))
2163
2164 if med['pk_brand'] is None:
2165 brand = u''
2166 else:
2167 if med['fake_brand']:
2168 brand = gmTools.coalesce(med['brand'], u'', _('%s (fake)'))
2169 else:
2170 brand = gmTools.coalesce(med['brand'], u'')
2171 self.SetCellValue(row_idx, 6, gmTools.wrap(text = brand, width = 35))
2172
2173 elif self.__grouping_mode == u'brand':
2174
2175 if med['pk_brand'] is None:
2176 self.__prev_cell_0 = None
2177 brand = gmTools.u_diameter
2178 else:
2179 if self.__prev_cell_0 == med['brand']:
2180 brand = u''
2181 else:
2182 self.__prev_cell_0 = med['brand']
2183 if med['fake_brand']:
2184 brand = gmTools.coalesce(med['brand'], u'', _('%s (fake)'))
2185 else:
2186 brand = gmTools.coalesce(med['brand'], u'')
2187 self.SetCellValue(row_idx, 0, gmTools.wrap(text = brand, width = 35))
2188
2189 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], u''))
2190 self.SetCellValue(row_idx, 2, med['substance'])
2191 self.SetCellValue(row_idx, 3, u'%s %s' % (med['amount'], med['unit']))
2192 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d'))
2193
2194 if med['is_long_term']:
2195 self.SetCellValue(row_idx, 5, gmTools.u_infinity)
2196 else:
2197 if med['discontinued'] is None:
2198 if med['duration'] is None:
2199 self.SetCellValue(row_idx, 5, u'')
2200 else:
2201 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
2202 else:
2203 self.SetCellValue(row_idx, 5, med['discontinued'].strftime('%Y-%m-%d'))
2204
2205 if med['pk_health_issue'] is None:
2206 issue = u'%s%s' % (
2207 gmTools.u_diameter,
2208 gmTools.coalesce(med['episode'], u'', u' (%s)')
2209 )
2210 else:
2211 issue = gmTools.coalesce(med['health_issue'], u'')
2212 self.SetCellValue(row_idx, 6, gmTools.wrap(text = issue, width = 40))
2213
2214 else:
2215 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode)
2216
2217 if med['notes'] is not None:
2218 self.SetCellValue(row_idx, 7, gmTools.wrap(text = med['notes'], width = 50))
2219
2220 if self.__filter_show_unapproved:
2221 self.SetCellValue (
2222 row_idx,
2223 len(labels),
2224 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, u'', u'?')
2225 )
2226
2227
2228
2229 self.AutoSize()
2230 self.EndBatch()
2231
2233 self.BeginBatch()
2234 self.ClearGrid()
2235
2236
2237 if self.GetNumberRows() > 0:
2238 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2239 if self.GetNumberCols() > 0:
2240 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2241 self.EndBatch()
2242 self.__row_data = {}
2243 self.__prev_cell_0 = None
2244
2246
2247 if len(self.__row_data) == 0:
2248 return
2249
2250 sel_rows = self.get_selected_rows()
2251 if len(sel_rows) != 1:
2252 return
2253
2254 drug_db = get_drug_database()
2255 if drug_db is None:
2256 return
2257
2258 intake = self.get_selected_data()[0]
2259 if intake['brand'] is None:
2260 drug_db.show_info_on_substance(substance_intake = intake)
2261 else:
2262 drug_db.show_info_on_drug(substance_intake = intake)
2263
2271
2274
2286
2298
2312
2315
2329
2343
2359
2363
2468
2469
2470
2472 self.CreateGrid(0, 1)
2473 self.EnableEditing(0)
2474 self.EnableDragGridSize(1)
2475 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
2476
2477 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
2478
2479 self.SetRowLabelSize(0)
2480 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2481
2482
2483
2485 return self.__patient
2486
2490
2491 patient = property(_get_patient, _set_patient)
2492
2494 return self.__grouping_mode
2495
2499
2500 grouping_mode = property(_get_grouping_mode, _set_grouping_mode)
2501
2503 return self.__filter_show_unapproved
2504
2508
2509 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved)
2510
2512 return self.__filter_show_inactive
2513
2517
2518 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive)
2519
2520
2521
2523
2524 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2525
2526
2527
2528
2529 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2530
2532 """Calculate where the mouse is and set the tooltip dynamically."""
2533
2534
2535
2536 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550 row, col = self.XYToCell(x, y)
2551
2552 if row == self.__prev_tooltip_row:
2553 return
2554
2555 self.__prev_tooltip_row = row
2556
2557 try:
2558 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
2559 except KeyError:
2560 pass
2561
2566
2567 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
2568
2569 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
2570
2571 """Panel holding a grid with current substances. Used as notebook page."""
2572
2579
2580
2581
2590
2591
2592
2594 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
2595 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_data_reget)
2596 gmDispatcher.connect(signal = u'substance_intake_mod_db', receiver = self._schedule_data_reget)
2597
2598
2599
2601 wx.CallAfter(self.__on_pre_patient_selection)
2602
2604 self._grid_substances.patient = None
2605
2608
2611
2614
2617
2620
2623
2626
2629
2632
2635
2638
2641
2644
2647
2650
2653
2654
2655
2656 if __name__ == '__main__':
2657
2658 if len(sys.argv) < 2:
2659 sys.exit()
2660
2661 if sys.argv[1] != 'test':
2662 sys.exit()
2663
2664 from Gnumed.pycommon import gmI18N
2665
2666 gmI18N.activate_locale()
2667 gmI18N.install_domain(domain = 'gnumed')
2668
2669
2670 app = wx.PyWidgetTester(size = (600, 600))
2671
2672 app.SetWidget(cSubstancePhraseWheel, -1)
2673 app.MainLoop()
2674
2675
2676