[med-svn] [openmolar] 01/01: Imported Upstream version 0.6.2
Dmitry Smirnov
onlyjob at moszumanska.debian.org
Sat Mar 14 02:23:51 UTC 2015
This is an automated email from the git hooks/post-receive script.
onlyjob pushed a commit to branch upstream
in repository openmolar.
commit da5d3d8 (upstream)
Author: Dmitry Smirnov <onlyjob at member.fsf.org>
Date: Sat Mar 14 02:18:36 2015
Imported Upstream version 0.6.2
---
MANIFEST | 15 +-
PKG-INFO | 2 +-
src/openmolar/__init__.py | 2 +-
src/openmolar/dbtools/accounts.py | 17 +-
src/openmolar/dbtools/daybook.py | 2 +-
src/openmolar/dbtools/estimates.py | 5 +
src/openmolar/dbtools/forum.py | 18 +-
src/openmolar/dbtools/medhist.py | 217 ++
src/openmolar/dbtools/patient_class.py | 80 +-
src/openmolar/dbtools/patient_write_changes.py | 55 +-
src/openmolar/dbtools/queries.py | 2 +
src/openmolar/dbtools/standard_letter.py | 153 +
src/openmolar/dbtools/updateMH.py | 79 -
src/openmolar/ptModules/formatted_notes.py | 37 +-
src/openmolar/ptModules/standardletter.py | 83 -
src/openmolar/qt4gui/charts/charts_gui.py | 23 +-
.../qt4gui/compiled_uis/Ui_enter_letter_text.py | 68 -
src/openmolar/qt4gui/compiled_uis/Ui_main.py | 19 +-
.../qt4gui/compiled_uis/Ui_ortho_ref_wizard.py | 251 --
src/openmolar/qt4gui/compiled_uis/Ui_toothProps.py | 29 +-
.../qt4gui/customwidgets/completer_textedit.py | 122 +
src/openmolar/qt4gui/customwidgets/toothProps.py | 11 +-
.../dialogs/advanced_record_management_dialog.py | 3 +-
src/openmolar/qt4gui/dialogs/alter_todays_notes.py | 10 +-
.../qt4gui/dialogs/auto_address_dialog.py | 4 +-
src/openmolar/qt4gui/dialogs/child_smile_dialog.py | 13 +-
.../qt4gui/dialogs/clinician_select_dialog.py | 18 +-
.../qt4gui/dialogs/correspondence_dialog.py | 143 +
src/openmolar/qt4gui/dialogs/dialog_collection.py | 9 +-
.../qt4gui/dialogs/edit_standard_letters_dialog.py | 307 ++
.../qt4gui/dialogs/find_patient_dialog.py | 93 +-
src/openmolar/qt4gui/dialogs/med_notes_dialog.py | 145 -
.../qt4gui/dialogs/medical_history_dialog.py | 425 +++
src/openmolar/qt4gui/diary_widget.py | 3 +-
src/openmolar/qt4gui/fees/daybook_module.py | 3 -
src/openmolar/qt4gui/fees/fees_module.py | 29 +-
src/openmolar/qt4gui/fees/manipulate_plan.py | 26 +-
src/openmolar/qt4gui/forum_gui_module.py | 2 +-
src/openmolar/qt4gui/maingui.py | 179 +-
src/openmolar/qt4gui/new_patient_gui.py | 17 +-
src/openmolar/qt4gui/printing/bulk_mail.py | 32 +-
src/openmolar/qt4gui/printing/gp17/gp17_data.py | 1 +
src/openmolar/qt4gui/printing/letterprint.py | 2 +-
src/openmolar/qt4gui/printing/om_printing.py | 150 +-
src/openmolar/qt4gui/schema_updater.py | 14 +
src/openmolar/resources/icons/med.png | Bin 0 -> 340 bytes
src/openmolar/schema_upgrades/druglist.py | 3173 ++++++++++++++++++++
src/openmolar/schema_upgrades/schema2_9to3_0.py | 149 +
src/openmolar/schema_upgrades/schema3_0to3_1.py | 289 ++
src/openmolar/settings/fee_tables.py | 5 +-
src/openmolar/settings/localsettings.py | 44 +-
src/openmolar/settings/version.py | 2 +-
52 files changed, 5557 insertions(+), 1023 deletions(-)
diff --git a/MANIFEST b/MANIFEST
index 82411cf..f84516e 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -41,6 +41,7 @@ src/openmolar/dbtools/estimatesHistory.py
src/openmolar/dbtools/families.py
src/openmolar/dbtools/feescales.py
src/openmolar/dbtools/forum.py
+src/openmolar/dbtools/medhist.py
src/openmolar/dbtools/memos.py
src/openmolar/dbtools/nhs_claims.py
src/openmolar/dbtools/patient_class.py
@@ -53,8 +54,8 @@ src/openmolar/dbtools/recall.py
src/openmolar/dbtools/referral.py
src/openmolar/dbtools/schema_version.py
src/openmolar/dbtools/search.py
+src/openmolar/dbtools/standard_letter.py
src/openmolar/dbtools/treatment_course.py
-src/openmolar/dbtools/updateMH.py
src/openmolar/dbtools/writeNewCourse.py
src/openmolar/dbtools/writeNewPatient.py
src/openmolar/html/index.html
@@ -137,7 +138,6 @@ src/openmolar/ptModules/patientDetails.py
src/openmolar/ptModules/plan.py
src/openmolar/ptModules/planDetails.py
src/openmolar/ptModules/reception_summary.py
-src/openmolar/ptModules/standardletter.py
src/openmolar/ptModules/tooth_history.py
src/openmolar/qt4gui/__init__.py
src/openmolar/qt4gui/colours.py
@@ -171,7 +171,6 @@ src/openmolar/qt4gui/compiled_uis/Ui_codeChecker.py
src/openmolar/qt4gui/compiled_uis/Ui_customTreatment.py
src/openmolar/qt4gui/compiled_uis/Ui_daylist_print.py
src/openmolar/qt4gui/compiled_uis/Ui_diary_widget.py
-src/openmolar/qt4gui/compiled_uis/Ui_enter_letter_text.py
src/openmolar/qt4gui/compiled_uis/Ui_exam_wizard.py
src/openmolar/qt4gui/compiled_uis/Ui_finalise_appt_time.py
src/openmolar/qt4gui/compiled_uis/Ui_forumPost.py
@@ -180,7 +179,6 @@ src/openmolar/qt4gui/compiled_uis/Ui_main.py
src/openmolar/qt4gui/compiled_uis/Ui_medhist.py
src/openmolar/qt4gui/compiled_uis/Ui_newBPE.py
src/openmolar/qt4gui/compiled_uis/Ui_newCourse.py
-src/openmolar/qt4gui/compiled_uis/Ui_ortho_ref_wizard.py
src/openmolar/qt4gui/compiled_uis/Ui_patient_diary.py
src/openmolar/qt4gui/compiled_uis/Ui_patient_finder.py
src/openmolar/qt4gui/compiled_uis/Ui_payments.py
@@ -202,6 +200,7 @@ src/openmolar/qt4gui/customwidgets/aptOVcontrol.py
src/openmolar/qt4gui/customwidgets/calendars.py
src/openmolar/qt4gui/customwidgets/chainLabel.py
src/openmolar/qt4gui/customwidgets/chartwidget.py
+src/openmolar/qt4gui/customwidgets/completer_textedit.py
src/openmolar/qt4gui/customwidgets/confirming_check_box.py
src/openmolar/qt4gui/customwidgets/currency_label.py
src/openmolar/qt4gui/customwidgets/dent_hyg_selector.py
@@ -250,6 +249,7 @@ src/openmolar/qt4gui/dialogs/choose_tooth_dialog.py
src/openmolar/qt4gui/dialogs/clinician_select_dialog.py
src/openmolar/qt4gui/dialogs/close_course_dialog.py
src/openmolar/qt4gui/dialogs/complete_treatment_dialog.py
+src/openmolar/qt4gui/dialogs/correspondence_dialog.py
src/openmolar/qt4gui/dialogs/course_consistency_dialog.py
src/openmolar/qt4gui/dialogs/course_edit_dialog.py
src/openmolar/qt4gui/dialogs/course_history_options_dialog.py
@@ -263,6 +263,7 @@ src/openmolar/qt4gui/dialogs/document_dialog.py
src/openmolar/qt4gui/dialogs/duplicate_receipt_dialog.py
src/openmolar/qt4gui/dialogs/edit_practice_dialog.py
src/openmolar/qt4gui/dialogs/edit_referral_centres_dialog.py
+src/openmolar/qt4gui/dialogs/edit_standard_letters_dialog.py
src/openmolar/qt4gui/dialogs/edit_treatment_dialog.py
src/openmolar/qt4gui/dialogs/estimate_edit_dialog.py
src/openmolar/qt4gui/dialogs/exam_wizard.py
@@ -275,7 +276,7 @@ src/openmolar/qt4gui/dialogs/hygTreatWizard.py
src/openmolar/qt4gui/dialogs/implant_choice_dialog.py
src/openmolar/qt4gui/dialogs/initial_check_dialog.py
src/openmolar/qt4gui/dialogs/login_dialog.py
-src/openmolar/qt4gui/dialogs/med_notes_dialog.py
+src/openmolar/qt4gui/dialogs/medical_history_dialog.py
src/openmolar/qt4gui/dialogs/newBPE.py
src/openmolar/qt4gui/dialogs/newCourse.py
src/openmolar/qt4gui/dialogs/new_bridge_dialog.py
@@ -384,6 +385,7 @@ src/openmolar/resources/icons/kfm_home.png
src/openmolar/resources/icons/logo.png
src/openmolar/resources/icons/lower_implant.svg
src/openmolar/resources/icons/mail_new.png
+src/openmolar/resources/icons/med.png
src/openmolar/resources/icons/memos.png
src/openmolar/resources/icons/month.png
src/openmolar/resources/icons/number1.png
@@ -440,6 +442,7 @@ src/openmolar/resources/user_manual/styles.css
src/openmolar/resources/user_manual/treatment-planning.html
src/openmolar/schema_upgrades/__init__.py
src/openmolar/schema_upgrades/database_updater_thread.py
+src/openmolar/schema_upgrades/druglist.py
src/openmolar/schema_upgrades/schema1_0to1_1.py
src/openmolar/schema_upgrades/schema1_1to1_2.py
src/openmolar/schema_upgrades/schema1_2to1_3.py
@@ -459,6 +462,8 @@ src/openmolar/schema_upgrades/schema2_5to2_6.py
src/openmolar/schema_upgrades/schema2_6to2_7.py
src/openmolar/schema_upgrades/schema2_7to2_8.py
src/openmolar/schema_upgrades/schema2_8to2_9.py
+src/openmolar/schema_upgrades/schema2_9to3_0.py
+src/openmolar/schema_upgrades/schema3_0to3_1.py
src/openmolar/settings/__init__.py
src/openmolar/settings/allowed.py
src/openmolar/settings/appointment_shortcuts.py
diff --git a/PKG-INFO b/PKG-INFO
index 0bb11b9..24f079d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: openmolar
-Version: 0.6.0
+Version: 0.6.2
Summary: Open Source Dental Practice Management Software
Home-page: https://www.openmolar.com
Author: Neil Wallace
diff --git a/src/openmolar/__init__.py b/src/openmolar/__init__.py
index 9993dbe..2d7b0fd 100755
--- a/src/openmolar/__init__.py
+++ b/src/openmolar/__init__.py
@@ -29,7 +29,7 @@ import gettext
if "neil" in os.path.expanduser("~"):
FORMAT = \
- '%(levelname)s {%(filename)s:%(lineno)d} %(funcName)s - %(message)s'
+ '%(levelname)s {%(filename)s:%(lineno)d} %(funcName)s\t- %(message)s'
else:
FORMAT = '%(levelname)s - %(message)s'
diff --git a/src/openmolar/dbtools/accounts.py b/src/openmolar/dbtools/accounts.py
index dd4663b..1306ba2 100644
--- a/src/openmolar/dbtools/accounts.py
+++ b/src/openmolar/dbtools/accounts.py
@@ -29,13 +29,17 @@ module to retrieve a list of patients who owe money
from openmolar.settings import localsettings
from openmolar.connect import connect
-QUERY = '''select dnt1, serialno, cset, fname, sname, dob, memo, pd4,
-billdate, billtype, billct, courseno0,
+QUERY = '''select dnt1, new_patients.serialno, cset, fname, sname, dob, memo,
+tx_date, billdate, billtype, billct, cmpd,
(money0 + money1 + money9 + money10 - money2 - money3 - money8) as fees
-from new_patients join patient_money on serialno = patient_money.pt_sno
-join patient_dates on serialno = patient_dates.pt_sno
+from new_patients
+join patient_money on new_patients.serialno = patient_money.pt_sno
+join currtrtmt2 on new_patients.courseno0 = currtrtmt2.courseno
+join (select serialno, max(date) as tx_date from daybook group by serialno)
+as t on new_patients.serialno = t.serialno
where (money0 + money1 + money9 + money10 - money2 - money3 - money8) > 0
-order by pd4 desc'''
+order by tx_date desc
+'''
def details():
@@ -51,4 +55,5 @@ def details():
if __name__ == "__main__":
localsettings.initiate()
- print details()
+ for row in details():
+ print row
diff --git a/src/openmolar/dbtools/daybook.py b/src/openmolar/dbtools/daybook.py
index f0d432d..784a6fa 100644
--- a/src/openmolar/dbtools/daybook.py
+++ b/src/openmolar/dbtools/daybook.py
@@ -54,7 +54,7 @@ DETAILS_QUERY = '''select DATE_FORMAT(date,'%s'), daybook.serialno,
concat (fname, " ", sname), coursetype, dntid,
trtid, diagn, perio, anaes, misc, ndu, ndl, odu, odl, other, chart,
feesa, feesb, feesc, id
- from daybook join patients on daybook.serialno = patients.serialno
+ from daybook join new_patients on daybook.serialno = new_patients.serialno
where {{DENT CONDITIONS}}
date >= %%s and date <= %%s {{FILTERS}} order by date''' % (
localsettings.OM_DATE_FORMAT.replace("%", "%%"))
diff --git a/src/openmolar/dbtools/estimates.py b/src/openmolar/dbtools/estimates.py
index 37bf35d..06a8840 100644
--- a/src/openmolar/dbtools/estimates.py
+++ b/src/openmolar/dbtools/estimates.py
@@ -145,6 +145,11 @@ def update_daybook_after_estimate_change(values):
cursor.execute(query, (daybook_id,))
feesa, feesb = cursor.fetchone()
+ # this next situation occurs if all treatments hashes related to
+ # the daybook row have been deleted
+ if (feesa, feesb) == (None, None):
+ feesa, feesb = 0, 0
+
LOGGER.debug(
"updating row with feesa, feesb = %s and %s" %
(feesa, feesb))
diff --git a/src/openmolar/dbtools/forum.py b/src/openmolar/dbtools/forum.py
index 3520505..0d7c969 100644
--- a/src/openmolar/dbtools/forum.py
+++ b/src/openmolar/dbtools/forum.py
@@ -125,21 +125,17 @@ def getPosts(user=None, include_closed=False):
gets all active rows from a forum table
'''
global HIGHESTID
- filter = ""
- if not include_closed:
- filter += ' open '
+ conditions, values = ["open"], [not include_closed]
if user:
- if filter == "":
- filter += "and"
- filter += ' recipient = ' + user
- if filter != "":
- filter = "where " + filter
+ conditions.append('recipient')
+ values.append(user)
db = connect.connect()
cursor = db.cursor()
query = ('SELECT ix, parent_ix, topic, inits, fdate, recipient, comment '
- 'FROM forum %s ORDER BY parent_ix, ix' % filter)
+ 'FROM forum where %s ORDER BY parent_ix, ix' %
+ " and ".join(["%s=%%s"%val for val in conditions]))
- cursor.execute(query)
+ cursor.execute(query, values)
rows = cursor.fetchall()
cursor.close()
@@ -167,6 +163,6 @@ def getPosts(user=None, include_closed=False):
return retarg
if __name__ == "__main__":
- posts = getPosts()
+ posts = getPosts(user="NW")
for post in posts:
print post.parent_ix, post.ix, post.topic
diff --git a/src/openmolar/dbtools/medhist.py b/src/openmolar/dbtools/medhist.py
new file mode 100644
index 0000000..b31a842
--- /dev/null
+++ b/src/openmolar/dbtools/medhist.py
@@ -0,0 +1,217 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+'''
+this module provides read/write tools for medical history
+'''
+from collections import namedtuple
+import logging
+
+from openmolar.connect import connect
+from openmolar.settings import localsettings
+
+LOGGER = logging.getLogger("openmolar")
+
+ALL_MEDS_QUERY = 'select medication from medications'
+
+NEW_MED_QUERY = '''insert into medications (medication, warning) values (%s, %s)
+on duplicate key update medication=medication'''
+
+MH_QUERY = '''
+select ix, warning_card, medication_comments, allergies,
+respiratory,heart, diabetes, arthritis, bleeding, infectious_disease,
+endocarditis, liver, anaesthetic, joint_replacement, heart_surgery,
+brain_surgery, hospital, cjd, other, alert, chkdate, time_stamp
+from medhist where pt_sno = %s order by ix desc limit 1
+'''
+
+MEDS_QUERY = 'select med, details from medication_link where med_ix=%s'
+
+DELETE_MEDS_QUERY = 'delete from medication_link where med_ix=%s'
+
+INSERT_MEDS_QUERY = \
+ 'insert into medication_link (med_ix, med, details) values (%s, %s, %s)'
+
+UPDATE_CHKDATE_QUERY = "update medhist set chkdate=%s, modified_by=%s where ix=%s"
+
+PROPERTIES = ('ix', 'warning_card', 'medications',
+ 'medication_comments', 'allergies',
+ 'respiratory', 'heart', 'diabetes', 'arthritis', 'bleeding',
+ 'infectious_disease', 'endocarditis', 'liver', 'anaesthetic',
+ 'joint_replacement', 'heart_surgery', 'brain_surgery', 'hospital', 'cjd',
+ 'other', 'alert', 'chkdate', 'time_stamp')
+
+MedHist = namedtuple('MedHist', PROPERTIES)
+
+INSERT_QUERY = '''
+insert into medhist (pt_sno, warning_card,
+medication_comments, allergies, respiratory, heart, diabetes, arthritis,
+bleeding, infectious_disease, endocarditis, liver, anaesthetic,
+joint_replacement, heart_surgery, brain_surgery, hospital, cjd, other, alert,
+chkdate, modified_by)
+values (%s)''' % ", ".join(["%s" for val in PROPERTIES[:-1]])
+
+UPDATE_QUERY = '''
+update medhist set warning_card=%s,
+medication_comments=%s, allergies=%s, respiratory=%s, heart=%s, diabetes=%s,
+arthritis=%s, bleeding=%s, infectious_disease=%s, endocarditis=%s, liver=%s,
+anaesthetic=%s, joint_replacement=%s, heart_surgery=%s, brain_surgery=%s,
+hospital=%s, cjd=%s, other=%s, alert=%s, chkdate = %s, modified_by=%s
+where ix=%s'''
+
+
+NULLS = (None, "", {}) + \
+ ("", ) * (len(PROPERTIES) - 6) + (False, localsettings.currentDay(), None)
+
+
+def get_medications():
+ '''
+ get all medications currently stored in the database
+ (used for autocomplete function)
+ '''
+ db = connect()
+ cursor = db.cursor()
+ cursor.execute(ALL_MEDS_QUERY)
+ for row in cursor.fetchall():
+ yield row[0]
+ cursor.close()
+
+
+def get_mh(sno):
+ db = connect()
+ cursor = db.cursor()
+ cursor.execute(MH_QUERY, (sno,))
+ row = cursor.fetchone()
+ if row:
+ values = row[:2] + ({},) + row[2:]
+ med_hist = MedHist(*values)
+ cursor.execute(MEDS_QUERY, (med_hist.ix,))
+ for med, details in cursor.fetchall():
+ med_hist.medications[med] = "" if details is None else details
+ else:
+ med_hist = MedHist(*NULLS)
+ cursor.close()
+ return med_hist
+
+
+def update_chkdate(ix):
+ LOGGER.debug("marking mh %s as checked today", ix)
+ db = connect()
+ cursor = db.cursor()
+ result = cursor.execute(
+ UPDATE_CHKDATE_QUERY, (localsettings.currentDay(), localsettings.operator, ix))
+ cursor.close()
+ return result
+
+
+def insert_medication(medication, warning=False):
+ LOGGER.warning(
+ "inserting new medication '%s' into approved list", medication)
+ db = connect()
+ cursor = db.cursor()
+ result = cursor.execute(NEW_MED_QUERY, (medication, warning))
+ cursor.close()
+ return result
+
+
+def insert_mh(sno, mh):
+ assert isinstance(mh, MedHist), "bad object passed to insert mh"
+ db = connect()
+ cursor = db.cursor()
+ values = (sno,
+ mh.warning_card,
+ mh.medication_comments,
+ mh.allergies,
+ mh.respiratory,
+ mh.heart,
+ mh.diabetes,
+ mh.arthritis,
+ mh.bleeding,
+ mh.infectious_disease,
+ mh.endocarditis,
+ mh.liver,
+ mh.anaesthetic,
+ mh.joint_replacement,
+ mh.heart_surgery,
+ mh.brain_surgery,
+ mh.hospital,
+ mh.cjd,
+ mh.other,
+ mh.alert,
+ mh.chkdate,
+ localsettings.operator,
+ )
+ cursor.execute(INSERT_QUERY, values)
+ ix = db.insert_id()
+ cursor.executemany(INSERT_MEDS_QUERY,
+ [(ix, key, mh.medications[key]) for key in mh.medications])
+ cursor.close()
+
+
+def update_mh(ix, mh):
+ assert isinstance(mh, MedHist), "bad object passed to insert mh"
+ db = connect()
+ cursor = db.cursor()
+ values = (mh.warning_card,
+ mh.medication_comments,
+ mh.allergies,
+ mh.respiratory,
+ mh.heart,
+ mh.diabetes,
+ mh.arthritis,
+ mh.bleeding,
+ mh.infectious_disease,
+ mh.endocarditis,
+ mh.liver,
+ mh.anaesthetic,
+ mh.joint_replacement,
+ mh.heart_surgery,
+ mh.brain_surgery,
+ mh.hospital,
+ mh.cjd,
+ mh.other,
+ mh.alert,
+ mh.chkdate,
+ localsettings.operator,
+ ix
+ )
+ result = cursor.execute(UPDATE_QUERY, values)
+ cursor.execute(DELETE_MEDS_QUERY, (ix,))
+ cursor.executemany(INSERT_MEDS_QUERY,
+ [(ix, key, mh.medications[key]) for key in mh.medications])
+ cursor.close()
+ return result
+
+
+if __name__ == "__main__":
+ LOGGER.setLevel(logging.DEBUG)
+ get_medications()
+ mh_null = get_mh(0)
+ mh_valid = get_mh(1)
+
+ print mh_null
+ assert mh_null == MedHist(*NULLS), "null medical history shouldn't happen"
+ print mh_valid
+
+ insert_mh(1, mh_valid)
diff --git a/src/openmolar/dbtools/patient_class.py b/src/openmolar/dbtools/patient_class.py
index 7b98e75..ecc4b27 100644
--- a/src/openmolar/dbtools/patient_class.py
+++ b/src/openmolar/dbtools/patient_class.py
@@ -38,8 +38,9 @@ from openmolar.dbtools.plan_data import PlanData
from openmolar.dbtools.est_logger import EstLogger
from openmolar.dbtools import estimates as db_estimates
-from openmolar.dbtools.queries import \
- PATIENT_QUERY_FIELDS, PATIENT_QUERY, FUTURE_EXAM_QUERY, PSN_QUERY, FAMILY_COUNT_QUERY
+from openmolar.dbtools.queries import (
+ PATIENT_QUERY_FIELDS, PATIENT_QUERY, FUTURE_EXAM_QUERY,
+ PSN_QUERY, FAMILY_COUNT_QUERY, QUICK_MED_QUERY)
LOGGER = logging.getLogger("openmolar")
@@ -80,10 +81,6 @@ exemptionTableAtts = ('exemption', 'exempttext')
bpeTableAtts = ('bpedate', 'bpe')
bpeTableVals = (nullDate, '', ())
-mnhistTableAtts = ('chgdate', 'ix', 'note')
-
-notesTableAtts = ('lineno', 'line')
-
mouth = ['ul8', 'ul7', 'ul6', 'ul5', 'ul4', 'ul3', 'ul2', 'ul1',
'ur1', 'ur2', 'ur3', 'ur4', 'ur5', 'ur6', 'ur7', 'ur8',
'lr8', 'lr7', 'lr6', 'lr5', 'lr4', 'lr3', 'lr2', 'lr1',
@@ -133,7 +130,7 @@ class patient(object):
self.pd1 = None
self.pd2 = None
self.pd3 = None
- self.pd4 = None
+ self.pd4 = None # this field is no longer used (last treatment date)
self.pd5 = None
self.pd6 = None
self.pd7 = None
@@ -218,11 +215,6 @@ class patient(object):
self.transfer = 0
self.pstatus = None
- # TABLE 'mnhist'#######
- self.chgdate = nullDate # date YES None
- self.ix = 0 # tinyint(3) unsigned YES None
- self.note = '' # varchar(60) YES None
-
self.estimates = []
# from userdata
@@ -235,8 +227,8 @@ class patient(object):
self.bpedate = nullDate
self.chartdate = nullDate
self.notes_dict = {}
- self.MH = ()
self.MEDALERT = False
+ self.mh_chkdate = None
self.HIDDENNOTES = []
self.chartgrid = {}
self._fee_table = None
@@ -248,6 +240,7 @@ class patient(object):
self._most_recent_daybook_entry = None
self._has_exam_booked = None
self._previous_surnames = None
+ self.monies_reset = False
if self.serialno == 0:
return
@@ -299,15 +292,11 @@ class patient(object):
self.getNotesTuple()
- query = 'select drnm,adrtel,curmed,oldmed,allerg,heart,lungs,' +\
- 'liver,kidney,bleed,anaes,other,alert,chkdate from mednotes' +\
- ' where serialno=%s'
- cursor.execute(query, (self.serialno,))
-
- self.MH = cursor.fetchone()
- if self.MH is not None:
- self.MEDALERT = self.MH[12]
-
+ cursor.execute(QUICK_MED_QUERY, (self.serialno,))
+ try:
+ self.MEDALERT, self.mh_chkdate = cursor.fetchone()
+ except TypeError:
+ pass
cursor.close()
# db.close()
@@ -567,6 +556,7 @@ class patient(object):
self.chartgrid[pos] = decidmouth[mouth.index(pos)]
def apply_fees(self):
+ LOGGER.debug("Applying Fees")
if "N" in self.cset:
self.money0 = self.dbstate.money0 + self.fees_accrued
else:
@@ -618,29 +608,39 @@ class patient(object):
def resetAllMonies(self):
'''
- zero's everything except money11 (bad debt)
+ gets money1 and money 0 from apply_fees,
+ then equalises money3 and money2 accordingly.
+ zero's everything else
+ money11 (bad debt) is left unaltered.
'''
+ self.dbstate.money0 = 0
+ self.dbstate.money1 = 0
+ self.monies_reset = True
+
self.money0 = 0
self.money1 = 0
+ self.apply_fees()
self.money9 = 0
self.money10 = 0
- self.money2 = 0
- self.money3 = 0
+ self.money2 = self.money0
+ self.money3 = self.money1
self.money8 = 0
- self.dbstate.money0 = 0
- self.dbstate.money1 = 0
-
def nhs_claims(self, completed_only=True):
claims = []
for est in self.estimates:
if (est.csetype.startswith("N") and
(not completed_only or est.completed == 2)
- ):
+ ):
claims.append(est)
return claims
- def addHiddenNote(self, ntype, note="", attempt_delete=False):
+ def addHiddenNote(
+ self,
+ ntype,
+ note="",
+ attempt_delete=False,
+ one_only=False):
'''
re-written for schema 1.9
'''
@@ -693,6 +693,10 @@ class patient(object):
except ValueError:
self.HIDDENNOTES.append(HN)
+ if one_only:
+ while self.HIDDENNOTES.count(HN) > 1:
+ self.HIDDENNOTES.remove(HN)
+
def clearHiddenNotes(self):
self.HIDDENNOTES = []
@@ -701,6 +705,12 @@ class patient(object):
self.billct += 1
self.billtype = tone
+ def reset_billing(self):
+ if self.fees == 0:
+ self.billdate = None
+ self.billct = None
+ self.billtype = None
+
def treatmentOutstanding(self):
return (self.treatment_course and
self.treatment_course.has_treatment_outstanding)
@@ -771,10 +781,9 @@ class patient(object):
these are what is copied over into pt.dbstate
'''
return (patient_query_atts +
- exemptionTableAtts + bpeTableAtts + mnhistTableAtts +
- clinical_memos + (
+ exemptionTableAtts + bpeTableAtts + clinical_memos + (
"fees", "estimate_charges", "serialno", "estimates",
- "appt_prefs", "treatment_course", "chartgrid"))
+ "appt_prefs", "treatment_course", "chartgrid"))
@property
def USER_CHANGEABLE_ATTRIBUTES(self):
@@ -881,6 +890,11 @@ class patient(object):
if tx_hash == hash_:
yield est
+ @property
+ def address_tuple(self):
+ return (self.sname, self.addr1, self.addr2,
+ self.addr3, self.town, self.county,
+ self.pcde, self.tel1)
if __name__ == "__main__":
'''testing stuff'''
diff --git a/src/openmolar/dbtools/patient_write_changes.py b/src/openmolar/dbtools/patient_write_changes.py
index 6465e3b..4110c17 100644
--- a/src/openmolar/dbtools/patient_write_changes.py
+++ b/src/openmolar/dbtools/patient_write_changes.py
@@ -49,6 +49,10 @@ SYNOPSIS_INS_QUERY = '''
insert into clinical_memos (serialno, synopsis, author, datestamp)
values (%s, %s, %s, NOW())'''
+RESET_MONEY_QUERY = '''
+insert into patient_money (pt_sno, money0, money1) values (%s, %s, %s)
+on duplicate key update money0=%s, money1=%s'''
+
def all_changes(pt, changes):
LOGGER.debug("writing_changes to patient - %s" % str(changes))
@@ -319,48 +323,21 @@ def toNotes(serialno, newnotes):
def discreet_changes(pt, changes):
'''
- this updates only the selected atts
- (usually called by automated proc such as recalls...
- and accounts) only updates the patients table
+ this is actually a duplication of the all-changes function, and writes only
+ the changes passed.
+ the only reason to keep it is for the extra message posted to the log.
'''
LOGGER.warning("discreet changes sno=%s %s", pt.serialno, changes)
if not changes:
LOGGER.error("no changes passed")
- values = []
- for change in changes:
- values.append(pt.__dict__[change])
- values.append(pt.serialno)
-
- query = "update new_patients SET %s where serialno=%%s" % \
- ", ".join(["%s = %%s" % change for change in changes])
+ return all_changes(pt, changes)
- db = connect()
- cursor = db.cursor()
- cursor.execute(query, values)
- db.commit()
- cursor.close()
- return True
-
-def discreet_money_changes(pt, changes):
- '''
- update patient_monet attributes.
- '''
- LOGGER.warning("discreet_money_changes sno=%s %s", pt.serialno, changes)
- if not changes:
- LOGGER.error("no changes passed!")
- return
- values = []
- for change in changes:
- values.append(pt.__dict__[change])
- values.append(pt.serialno)
-
- query = "update patient_money SET %s where pt_sno=%%s" % \
- ", ".join(["%s = %%s" % change for change in changes])
-
- db = connect()
- cursor = db.cursor()
- cursor.execute(query, values)
- db.commit()
- cursor.close()
- return True
+def reset_money(pt):
+ if pt.monies_reset:
+ values = (pt.serialno,) + (pt.dbstate.money0, pt.dbstate.money1) * 2
+ db = connect()
+ cursor = db.cursor()
+ cursor.execute(RESET_MONEY_QUERY, values)
+ cursor.close()
+ return False
diff --git a/src/openmolar/dbtools/queries.py b/src/openmolar/dbtools/queries.py
index 3125d34..c6fb165 100644
--- a/src/openmolar/dbtools/queries.py
+++ b/src/openmolar/dbtools/queries.py
@@ -51,3 +51,5 @@ and (code0="EXAM" or code1="EXAM" or code2="EXAM") and adate >= CURDATE()'''
PSN_QUERY = "select psn from previous_snames where serialno=%s order by ix desc"
FAMILY_COUNT_QUERY = "select count(*) from new_patients where familyno=%s"
+
+QUICK_MED_QUERY = 'select alert, chkdate from medhist where pt_sno=%s order by ix desc limit 1'
diff --git a/src/openmolar/dbtools/standard_letter.py b/src/openmolar/dbtools/standard_letter.py
new file mode 100644
index 0000000..f87ba9f
--- /dev/null
+++ b/src/openmolar/dbtools/standard_letter.py
@@ -0,0 +1,153 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+import logging
+from collections import namedtuple
+
+from openmolar.connect import connect
+from openmolar.settings import localsettings
+
+LOGGER = logging.getLogger("openmolar")
+
+QUERY = \
+ "SELECT description, body_text, footer FROM standard_letters ORDER BY description"
+
+INSERT_QUERY = '''INSERT INTO standard_letters
+(description, body_text, footer) VALUES (%s, %s, %s)
+ON DUPLICATE KEY UPDATE body_text=%s, footer=%s'''
+
+DELETE_QUERY = 'DELETE FROM standard_letters WHERE description=%s'
+
+StandardLetter = namedtuple(
+ 'StandardLetter',
+ ('description',
+ 'text',
+ 'footer'))
+
+
+TEMPLATE = '''
+<html>
+<body>
+<!-- top margin -->
+%s
+<!-- end of top margin -->
+<!-- address -->
+<b>
+%%s<br />
+%%s
+</b>
+<!-- end of address -->
+<br /><br />
+<!-- date -->
+%%s
+<!-- end of date -->
+%s
+<!-- salutation -->
+%%s %%s,
+<!-- end of salutation.. -->
+<!-- letter body -->
+%s
+<!-- end of letter body -->
+<!-- sign off -->
+%%s
+<!-- end of sign off -->
+%s
+<!-- footer -->
+<!-- end of footer -->
+</body>
+</html>
+''' % ("<br />" * 6, "<br />" * 4, "<br />" * (12), "<br />" * 6)
+
+
+def getHtml(pt):
+ return TEMPLATE % (pt.name,
+ pt.address.replace("\n", "<br />"),
+ localsettings.longDate(localsettings.currentDay()),
+ _("Dear"),
+ pt.name.title(),
+ _("Yours Sincerely")
+ )
+
+
+def get_standard_letters():
+ db = connect()
+ cursor = db.cursor()
+ cursor.execute(QUERY)
+ for row in cursor.fetchall():
+ yield StandardLetter(*row)
+ cursor.close()
+
+
+def insert_letter(letter):
+ db = connect()
+ cursor = db.cursor()
+ result = cursor.execute(
+ INSERT_QUERY,
+ (letter.description, letter.text,
+ letter.footer, letter.text, letter.footer)
+ )
+ cursor.close()
+ if result == 0:
+ LOGGER.error("insert_letter failed!")
+ elif result == 1:
+ LOGGER.info("insert_letter worked!")
+ elif result == 2:
+ LOGGER.warning("insert_letter updated an existing letter!")
+ return result in (1, 2)
+
+
+def delete_letter(letter):
+ db = connect()
+ cursor = db.cursor()
+ result = cursor.execute(DELETE_QUERY, letter.description)
+ cursor.close()
+ return result
+
+
+def insert_letters(letters):
+ for letter in letters:
+ insert_letter(letter)
+
+
+def delete_letters(letters):
+ for letter in letters:
+ delete_letter(letter)
+
+def _test():
+ from openmolar.dbtools import patient_class
+ pt = patient_class.patient(1)
+ return getHtml(pt)
+
+def _test2():
+ letter = StandardLetter("test", "test body", "footer")
+ insert_letter(letter)
+ delete_letter(letter)
+ for letter in get_standard_letters():
+ LOGGER.debug(letter.description)
+
+
+if __name__ == "__main__":
+ LOGGER.setLevel(logging.DEBUG)
+ print _test().encode("ascii", "replace")
+ _test2()
\ No newline at end of file
diff --git a/src/openmolar/dbtools/updateMH.py b/src/openmolar/dbtools/updateMH.py
deleted file mode 100644
index 4890ed0..0000000
--- a/src/openmolar/dbtools/updateMH.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# ############################################################################ #
-# # # #
-# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
-# # # #
-# # This file is part of OpenMolar. # #
-# # # #
-# # OpenMolar is free software: you can redistribute it and/or modify # #
-# # it under the terms of the GNU General Public License as published by # #
-# # the Free Software Foundation, either version 3 of the License, or # #
-# # (at your option) any later version. # #
-# # # #
-# # OpenMolar is distributed in the hope that it will be useful, # #
-# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
-# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
-# # GNU General Public License for more details. # #
-# # # #
-# # You should have received a copy of the GNU General Public License # #
-# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
-# # # #
-# ############################################################################ #
-
-import MySQLdb
-import sys
-from openmolar.connect import connect
-from openmolar.settings import localsettings
-
-
-def write(sno, data):
- db = connect()
- cursor = db.cursor()
-
- result = True
- query = 'insert into mednotes (serialno,drnm,adrtel,curmed,oldmed,allerg,heart,lungs,liver,kidney,bleed,anaes,other,alert'
- dateToWrite = data[13]
- if dateToWrite is None:
- query += ") values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
- values = (int(sno),) + data[:13]
- else:
- query += ",chkdate) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
- values = (int(sno),) + data[:13] + (dateToWrite,)
- print values
- try:
- cursor.execute("delete from mednotes where serialno=%d" % sno)
- cursor.execute(query, values)
- except Exception as e:
- print e
- result = False
- db.commit()
- cursor.close()
- # db.close()
-
- return result
-
-
-def writeHist(sno, data):
- db = connect()
- cursor = db.cursor()
-
- for ix, note in data:
- query = '''insert into mnhist (serialno,chgdate,ix,note)
- values (%s, NOW(), %s, %s)'''
- values = (sno, ix, note)
- cursor.execute(query, values)
- db.commit()
- cursor.close()
- # db.close()
-
- return True
-
-if __name__ == "__main__":
- import datetime
- newdata = (
- "doctor", "address", "curmeds", "pastmeds", "allergies", "heart", "lungs", "liver", "bleeding", "Kidneys",
- "ops", "other", True, datetime.date.today())
- write(11956, newdata)
- writeHist(11956, ((140, "new doctor"),))
diff --git a/src/openmolar/ptModules/formatted_notes.py b/src/openmolar/ptModules/formatted_notes.py
index 4b50318..892f2f7 100644
--- a/src/openmolar/ptModules/formatted_notes.py
+++ b/src/openmolar/ptModules/formatted_notes.py
@@ -101,9 +101,13 @@ def get_notes_for_date(lines, full_notes=False):
"<", "<").replace(">", ">")
else:
if "TC" in ntype:
- txs.append((ntype, noteline))
+ txs.append((ntype, noteline.strip("\n")))
elif ntype == "UNCOMPLETED":
rev_txs.append((ntype, noteline))
+ elif ntype == "UPDATED:Medical Notes":
+ mh_message = (_("MED"), noteline.strip("\n"))
+ if not mh_message in txs:
+ txs.insert(0, mh_message)
elif full_notes:
if "RECEIVED" in ntype:
receipt_text = noteline.replace("sundries 0.00", "")
@@ -142,9 +146,13 @@ def get_rec_summary(lines):
'''
note = ""
for ntype, noteline in lines:
+ LOGGER.debug("%s %s", ntype, noteline)
if "PRINTED" in ntype:
note += '<img src=%s height="12" align="right"> %s<br />' % (
localsettings.printer_png, noteline)
+ elif "UPDATED" in ntype:
+ note += '<img src=%s height="12" align="right"> %s<br />' % (
+ localsettings.medical_png, noteline)
elif "RECEIVED:" in ntype:
noteline = noteline.replace("sundries 0.00", "")
noteline = noteline.replace("treatment 0.00", "")
@@ -193,10 +201,10 @@ def notes(notes_dict, full_notes=True):
retarg = HEADER + '''
<table class="notes_table">
<tr>
- <th class = "date">Date</th>
- <th class = "ops">ops</th>
- <th class = "tx">Tx</th>
- <th class = "notes">Notes</th>
+ <th class="date">Date</th>
+ <th class="ops">ops</th>
+ <th class="tx">Tx</th>
+ <th class="notes">Notes</th>
'''
keys = notes_dict.keys()
@@ -204,7 +212,7 @@ def notes(notes_dict, full_notes=True):
if full_notes and show_metadata:
retarg += '<th class="reception">metadata</th>'
- retarg += '</tr>'
+ retarg += '</tr>\n'
previousdate = "" # necessary to group notes on same day
rowspan = 1
@@ -213,14 +221,16 @@ def notes(notes_dict, full_notes=True):
date, op = key
data = notes_dict[key]
tx, notes, metadata = get_notes_for_date(data, full_notes)
- newline += "<tr>"
+ if tx == "" and notes == "" and not show_metadata:
+ continue
+ newline += "<tr>\n"
if date != previousdate:
previousdate = date
rowspan = 1
retarg += newline
link = ""
- newline = '<td class="date">%s %s</td>' % (
+ newline = ' <td class="date">%s %s</td>' % (
localsettings.notesDate(date), link)
else:
# alter the previous html, so that the rows are spanned
@@ -235,14 +245,15 @@ def notes(notes_dict, full_notes=True):
op == localsettings.operator):
subline += '<br /><a href="edit_notes?||SNO||">%s</a>' % _("Edit")
- newline += '''%s</td>
+ newline += '''
+ %s</td>
<td class="tx">%s</td>
<td width="70%%" class="notes">%s</td>''' % (subline, tx, notes)
if show_metadata:
- newline += '<td class="reception">%s</td></tr>' % metadata
+ newline += '<td class="reception">%s</td>\n</tr>\n' % metadata
else:
- newline += '</tr>'
+ newline += '\n</tr>\n'
retarg += newline
retarg += '</table></div></body></html>'
@@ -264,6 +275,7 @@ def todays_notes(serialno):
return html
if __name__ == "__main__":
+ import datetime
LOGGER.setLevel(logging.DEBUG)
from openmolar.dbtools import patient_class
try:
@@ -273,3 +285,6 @@ if __name__ == "__main__":
notes_ = notes(patient_class.patient(serialno).notes_dict)
print notes_.encode("ascii", "replace")
+ notes = rec_notes(
+ patient_class.patient(serialno).notes_dict, datetime.date(2010,1,1))
+ print notes_.encode("ascii", "replace")
diff --git a/src/openmolar/ptModules/standardletter.py b/src/openmolar/ptModules/standardletter.py
deleted file mode 100644
index 9db82a5..0000000
--- a/src/openmolar/ptModules/standardletter.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# ############################################################################ #
-# # # #
-# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
-# # # #
-# # This file is part of OpenMolar. # #
-# # # #
-# # OpenMolar is free software: you can redistribute it and/or modify # #
-# # it under the terms of the GNU General Public License as published by # #
-# # the Free Software Foundation, either version 3 of the License, or # #
-# # (at your option) any later version. # #
-# # # #
-# # OpenMolar is distributed in the hope that it will be useful, # #
-# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
-# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
-# # GNU General Public License for more details. # #
-# # # #
-# # You should have received a copy of the GNU General Public License # #
-# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
-# # # #
-# ############################################################################ #
-
-import time
-import datetime
-
-
-def getHtml(pt):
- # try:
- retarg = "<html><body>"
- retarg += "<br />" * 6
- retarg += "<b>%s. %s %s<br />" % (
- pt.title.title(),
- pt.fname.title(),
- pt.sname.title())
- for val in (pt.addr1.title(), pt.addr2.title(), pt.addr3.title(), pt.town, pt.county.title(), pt.pcde):
- if str(val) != "":
- retarg += str(val) + "<br />"
- retarg += "</b>" + "<br />" * 2
- today = time.localtime()[:3]
- d = datetime.date(today[0], today[1], today[2])
- retarg += "%s, " % (
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- "Sunday")[d.weekday()]
- retarg += "%s " % d.day
- retarg += "%s, " % (
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December")[d.month - 1]
- retarg += '%s <br /><br />' % d.year
- retarg += "Dear %s. %s,<br />" % (
- pt.title.title(),
- pt.sname.title())
- #
- #-- this next line is used to insert text for estimates
- #-- do not change
- retarg += "<br />" * (12)
- #
- retarg += "Yours Sincerely," + "<br />" * 4
- retarg += "</body></html>"
- return retarg
- # except Exception,e:
- # return False
-
-if __name__ == "__main__":
- from openmolar.dbtools import patient_class
- pt = patient_class.patient(4)
- print getHtml(pt)
diff --git a/src/openmolar/qt4gui/charts/charts_gui.py b/src/openmolar/qt4gui/charts/charts_gui.py
index c86c692..002e42f 100644
--- a/src/openmolar/qt4gui/charts/charts_gui.py
+++ b/src/openmolar/qt4gui/charts/charts_gui.py
@@ -30,6 +30,7 @@ from __future__ import division
import copy
import logging
+import re
from PyQt4 import QtGui, QtCore
from openmolar.settings import localsettings
@@ -202,14 +203,20 @@ def selectChartedTooth(om_gui, x, y):
'''
only one tooth can be 'selected'
'''
- om_gui.ui.planChartWidget.setSelected(x, y, showSelection=
- om_gui.selectedChartWidget == "pl")
-
- om_gui.ui.completedChartWidget.setSelected(x, y, showSelection=
- om_gui.selectedChartWidget == "cmp")
-
- om_gui.ui.staticChartWidget.setSelected(x, y, showSelection=
- om_gui.selectedChartWidget == "st")
+ om_gui.ui.planChartWidget.setSelected(
+ x,
+ y,
+ showSelection=om_gui.selectedChartWidget == "pl")
+
+ om_gui.ui.completedChartWidget.setSelected(
+ x,
+ y,
+ showSelection=om_gui.selectedChartWidget == "cmp")
+
+ om_gui.ui.staticChartWidget.setSelected(
+ x,
+ y,
+ showSelection=om_gui.selectedChartWidget == "st")
def bpe_table(om_gui, arg):
diff --git a/src/openmolar/qt4gui/compiled_uis/Ui_enter_letter_text.py b/src/openmolar/qt4gui/compiled_uis/Ui_enter_letter_text.py
deleted file mode 100644
index 95cd4b2..0000000
--- a/src/openmolar/qt4gui/compiled_uis/Ui_enter_letter_text.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file '/home/neil/openmolar/openmolar1/src/openmolar/qt-designer/enter_letter_text.ui'
-#
-# Created: Wed Nov 6 23:05:24 2013
-# by: PyQt4 UI code generator 4.10.3
-#
-# WARNING! All changes made in this file will be lost!
-
-from PyQt4 import QtCore, QtGui
-
-try:
- _fromUtf8 = QtCore.QString.fromUtf8
-except AttributeError:
- def _fromUtf8(s):
- return s
-
-
-class Ui_Dialog(object):
-
- def setupUi(self, Dialog):
- Dialog.setObjectName(_fromUtf8("Dialog"))
- Dialog.setWindowModality(QtCore.Qt.NonModal)
- Dialog.resize(644, 641)
- Dialog.setSizeGripEnabled(True)
- self.gridLayout = QtGui.QGridLayout(Dialog)
- self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
- self.label = QtGui.QLabel(Dialog)
- self.label.setObjectName(_fromUtf8("label"))
- self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
- self.textEdit = QtGui.QTextEdit(Dialog)
- self.textEdit.setObjectName(_fromUtf8("textEdit"))
- self.gridLayout.addWidget(self.textEdit, 1, 0, 1, 1)
- self.buttonBox = QtGui.QDialogButtonBox(Dialog)
- self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(
- QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Save)
- self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
- self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1)
-
- self.retranslateUi(Dialog)
- QtCore.QObject.connect(
- self.buttonBox,
- QtCore.SIGNAL(_fromUtf8("accepted()")),
- Dialog.accept)
- QtCore.QObject.connect(
- self.buttonBox,
- QtCore.SIGNAL(_fromUtf8("rejected()")),
- Dialog.reject)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- Dialog.setWindowTitle(_("Letter Text Entry"))
- self.label.setText(
- _("<b>Please enter the body text for your letter here.</b>"))
-
-
-if __name__ == "__main__":
- import gettext
- gettext.install("openmolar")
- import sys
- app = QtGui.QApplication(sys.argv)
- Dialog = QtGui.QDialog()
- ui = Ui_Dialog()
- ui.setupUi(Dialog)
- Dialog.show()
- sys.exit(app.exec_())
diff --git a/src/openmolar/qt4gui/compiled_uis/Ui_main.py b/src/openmolar/qt4gui/compiled_uis/Ui_main.py
index fbaae2e..4e9bf4e 100644
--- a/src/openmolar/qt4gui/compiled_uis/Ui_main.py
+++ b/src/openmolar/qt4gui/compiled_uis/Ui_main.py
@@ -3,7 +3,7 @@
# Form implementation generated from reading ui file '/home/neil/openmolar/openmolar1/src/openmolar/qt-designer/main.ui'
#
-# Created: Mon Jun 23 13:41:48 2014
+# Created: Wed Jul 2 22:18:57 2014
# by: PyQt4 UI code generator 4.11
#
# WARNING! All changes made in this file will be lost!
@@ -1500,6 +1500,8 @@ class Ui_MainWindow(object):
self.chartsTableWidget.setSelectionBehavior(
QtGui.QAbstractItemView.SelectItems)
self.chartsTableWidget.setObjectName(_fromUtf8("chartsTableWidget"))
+ self.chartsTableWidget.setColumnCount(0)
+ self.chartsTableWidget.setRowCount(0)
self.horizontalLayout_4.addWidget(self.chartsTableWidget)
self.stackedWidget.addWidget(self.table)
self.charts = QtGui.QWidget()
@@ -2363,6 +2365,8 @@ class Ui_MainWindow(object):
QtGui.QAbstractItemView.SelectRows)
self.accounts_tableWidget.setObjectName(
_fromUtf8("accounts_tableWidget"))
+ self.accounts_tableWidget.setColumnCount(0)
+ self.accounts_tableWidget.setRowCount(0)
self.gridLayout_9.addWidget(self.accounts_tableWidget, 1, 0, 1, 5)
spacerItem20 = QtGui.QSpacerItem(
746,
@@ -2965,6 +2969,9 @@ class Ui_MainWindow(object):
self.actionInsert_Regular_Blocks = QtGui.QAction(MainWindow)
self.actionInsert_Regular_Blocks.setObjectName(
_fromUtf8("actionInsert_Regular_Blocks"))
+ self.actionEdit_Standard_Letters = QtGui.QAction(MainWindow)
+ self.actionEdit_Standard_Letters.setObjectName(
+ _fromUtf8("actionEdit_Standard_Letters"))
self.menuMenu.addAction(self.action_Open_Patient)
self.menuMenu.addAction(self.action_save_patient)
self.menuMenu.addSeparator()
@@ -3006,18 +3013,19 @@ class Ui_MainWindow(object):
self.menuTools.addSeparator()
self.menuTools.addAction(self.actionFix_Locked_New_Course_of_Treatment)
self.menuTools.addSeparator()
- self.menuTools.addAction(self.actionSet_Clinician)
self.menuTools.addAction(self.actionSet_Assistant)
+ self.menuTools.addAction(self.actionSet_Clinician)
self.menuTools.addAction(self.actionSet_Surgery_Number)
+ self.menuTools.addAction(self.actionReset_Supervisor_Password)
self.menuTools.addSeparator()
+ self.menuTools.addAction(self.actionEdit_Feescales)
self.menuTools.addAction(self.actionEdit_Phrasebooks)
self.menuTools.addAction(self.actionEdit_Referral_Centres)
- self.menuTools.addAction(self.actionEdit_Feescales)
+ self.menuTools.addAction(self.actionEdit_Standard_Letters)
+ self.menuTools.addAction(self.actionEdit_Practice_Details)
self.menuTools.addSeparator()
- self.menuTools.addAction(self.actionReset_Supervisor_Password)
self.menuTools.addAction(self.actionAdd_User)
self.menuTools.addAction(self.actionAdd_Clinician)
- self.menuTools.addAction(self.actionEdit_Practice_Details)
self.menu_Appointments.addAction(
self.actionClear_Today_s_Emergency_Slots)
self.menu_Appointments.addSeparator()
@@ -3553,6 +3561,7 @@ class Ui_MainWindow(object):
self.actionClear_Today_s_Emergency_Slots.setText(
_("Clear Today\'s Emergency Slots"))
self.actionInsert_Regular_Blocks.setText(_("Insert Regular Blocks"))
+ self.actionEdit_Standard_Letters.setText(_("Edit Standard Letters"))
from PyQt4 import QtWebKit
from openmolar.qt4gui import resources_rc
diff --git a/src/openmolar/qt4gui/compiled_uis/Ui_ortho_ref_wizard.py b/src/openmolar/qt4gui/compiled_uis/Ui_ortho_ref_wizard.py
deleted file mode 100644
index 135bffc..0000000
--- a/src/openmolar/qt4gui/compiled_uis/Ui_ortho_ref_wizard.py
+++ /dev/null
@@ -1,251 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file '/home/neil/openmolar/openmolar1/src/openmolar/qt-designer/ortho_ref_wizard.ui'
-#
-# Created: Wed Nov 6 23:05:24 2013
-# by: PyQt4 UI code generator 4.10.3
-#
-# WARNING! All changes made in this file will be lost!
-
-from PyQt4 import QtCore, QtGui
-
-try:
- _fromUtf8 = QtCore.QString.fromUtf8
-except AttributeError:
- def _fromUtf8(s):
- return s
-
-
-class Ui_Dialog(object):
-
- def setupUi(self, Dialog):
- Dialog.setObjectName(_fromUtf8("Dialog"))
- Dialog.resize(910, 594)
- Dialog.setMinimumSize(QtCore.QSize(0, 0))
- Dialog.setMaximumSize(QtCore.QSize(16777215, 16777215))
- self.gridLayout_5 = QtGui.QGridLayout(Dialog)
- self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5"))
- self.groupBox_5 = QtGui.QGroupBox(Dialog)
- self.groupBox_5.setMinimumSize(QtCore.QSize(0, 0))
- self.groupBox_5.setMaximumSize(QtCore.QSize(16777215, 16777215))
- self.groupBox_5.setObjectName(_fromUtf8("groupBox_5"))
- self.gridLayout_4 = QtGui.QGridLayout(self.groupBox_5)
- self.gridLayout_4.setMargin(2)
- self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4"))
- self.dh_plainTextEdit = QtGui.QPlainTextEdit(self.groupBox_5)
- sizePolicy = QtGui.QSizePolicy(
- QtGui.QSizePolicy.Expanding,
- QtGui.QSizePolicy.Preferred)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(
- self.dh_plainTextEdit.sizePolicy().hasHeightForWidth())
- self.dh_plainTextEdit.setSizePolicy(sizePolicy)
- self.dh_plainTextEdit.setMaximumSize(QtCore.QSize(16777215, 16777215))
- self.dh_plainTextEdit.setObjectName(_fromUtf8("dh_plainTextEdit"))
- self.gridLayout_4.addWidget(self.dh_plainTextEdit, 0, 0, 1, 2)
- self.gridLayout_5.addWidget(self.groupBox_5, 2, 1, 1, 1)
- self.frame = QtGui.QFrame(Dialog)
- self.frame.setFrameShape(QtGui.QFrame.StyledPanel)
- self.frame.setFrameShadow(QtGui.QFrame.Raised)
- self.frame.setObjectName(_fromUtf8("frame"))
- self.gridLayout = QtGui.QGridLayout(self.frame)
- self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
- self.label_2 = QtGui.QLabel(self.frame)
- self.label_2.setObjectName(_fromUtf8("label_2"))
- self.gridLayout.addWidget(self.label_2, 1, 1, 1, 1)
- self.ref1_radioButton = QtGui.QRadioButton(self.frame)
- self.ref1_radioButton.setChecked(True)
- self.ref1_radioButton.setObjectName(_fromUtf8("ref1_radioButton"))
- self.gridLayout.addWidget(self.ref1_radioButton, 0, 0, 1, 1)
- spacerItem = QtGui.QSpacerItem(
- 68,
- 20,
- QtGui.QSizePolicy.Expanding,
- QtGui.QSizePolicy.Minimum)
- self.gridLayout.addItem(spacerItem, 0, 1, 1, 2)
- self.ref2_radioButton = QtGui.QRadioButton(self.frame)
- self.ref2_radioButton.setObjectName(_fromUtf8("ref2_radioButton"))
- self.gridLayout.addWidget(self.ref2_radioButton, 1, 0, 1, 1)
- self.dateEdit = QtGui.QDateEdit(self.frame)
- self.dateEdit.setEnabled(False)
- self.dateEdit.setObjectName(_fromUtf8("dateEdit"))
- self.gridLayout.addWidget(self.dateEdit, 1, 2, 1, 1)
- self.tx_checkBox = QtGui.QCheckBox(self.frame)
- self.tx_checkBox.setObjectName(_fromUtf8("tx_checkBox"))
- self.gridLayout.addWidget(self.tx_checkBox, 2, 0, 1, 3)
- self.gridLayout_5.addWidget(self.frame, 0, 0, 1, 1)
- self.chart_groupBox = QtGui.QGroupBox(Dialog)
- self.chart_groupBox.setMinimumSize(QtCore.QSize(0, 80))
- self.chart_groupBox.setObjectName(_fromUtf8("chart_groupBox"))
- self.gridLayout_5.addWidget(self.chart_groupBox, 0, 1, 1, 1)
- self.buttonBox = QtGui.QDialogButtonBox(Dialog)
- self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(
- QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
- self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
- self.gridLayout_5.addWidget(self.buttonBox, 4, 0, 1, 3)
- self.groupBox_4 = QtGui.QGroupBox(Dialog)
- self.groupBox_4.setMaximumSize(QtCore.QSize(16777215, 16777215))
- self.groupBox_4.setObjectName(_fromUtf8("groupBox_4"))
- self.gridLayout_3 = QtGui.QGridLayout(self.groupBox_4)
- self.gridLayout_3.setMargin(2)
- self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3"))
- self.mh__plainTextEdit = QtGui.QPlainTextEdit(self.groupBox_4)
- sizePolicy = QtGui.QSizePolicy(
- QtGui.QSizePolicy.Expanding,
- QtGui.QSizePolicy.Preferred)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(
- self.mh__plainTextEdit.sizePolicy(
- ).hasHeightForWidth(
- ))
- self.mh__plainTextEdit.setSizePolicy(sizePolicy)
- self.mh__plainTextEdit.setMaximumSize(QtCore.QSize(16777215, 16777215))
- self.mh__plainTextEdit.setObjectName(_fromUtf8("mh__plainTextEdit"))
- self.gridLayout_3.addWidget(self.mh__plainTextEdit, 0, 0, 1, 1)
- self.gridLayout_5.addWidget(self.groupBox_4, 3, 1, 1, 1)
- self.groupBox = QtGui.QGroupBox(Dialog)
- sizePolicy = QtGui.QSizePolicy(
- QtGui.QSizePolicy.Preferred,
- QtGui.QSizePolicy.MinimumExpanding)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(
- self.groupBox.sizePolicy().hasHeightForWidth())
- self.groupBox.setSizePolicy(sizePolicy)
- self.groupBox.setObjectName(_fromUtf8("groupBox"))
- self.verticalLayout_2 = QtGui.QVBoxLayout(self.groupBox)
- self.verticalLayout_2.setSpacing(2)
- self.verticalLayout_2.setMargin(2)
- self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
- self.groupBox_2 = QtGui.QGroupBox(self.groupBox)
- self.groupBox_2.setObjectName(_fromUtf8("groupBox_2"))
- self.verticalLayout_3 = QtGui.QVBoxLayout(self.groupBox_2)
- self.verticalLayout_3.setMargin(2)
- self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
- self.crowding5_radioButton = QtGui.QRadioButton(self.groupBox_2)
- self.crowding5_radioButton.setObjectName(
- _fromUtf8("crowding5_radioButton"))
- self.verticalLayout_3.addWidget(self.crowding5_radioButton)
- self.crowding4_radioButton = QtGui.QRadioButton(self.groupBox_2)
- self.crowding4_radioButton.setObjectName(
- _fromUtf8("crowding4_radioButton"))
- self.verticalLayout_3.addWidget(self.crowding4_radioButton)
- self.crowding3_radioButton = QtGui.QRadioButton(self.groupBox_2)
- self.crowding3_radioButton.setObjectName(
- _fromUtf8("crowding3_radioButton"))
- self.verticalLayout_3.addWidget(self.crowding3_radioButton)
- self.crowding2_radioButton = QtGui.QRadioButton(self.groupBox_2)
- self.crowding2_radioButton.setObjectName(
- _fromUtf8("crowding2_radioButton"))
- self.verticalLayout_3.addWidget(self.crowding2_radioButton)
- self.crowding1_radioButton = QtGui.QRadioButton(self.groupBox_2)
- self.crowding1_radioButton.setObjectName(
- _fromUtf8("crowding1_radioButton"))
- self.verticalLayout_3.addWidget(self.crowding1_radioButton)
- self.verticalLayout_2.addWidget(self.groupBox_2)
- self.groupBox_3 = QtGui.QGroupBox(self.groupBox)
- self.groupBox_3.setObjectName(_fromUtf8("groupBox_3"))
- self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_3)
- self.gridLayout_2.setMargin(2)
- self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
- self.label = QtGui.QLabel(self.groupBox_3)
- self.label.setObjectName(_fromUtf8("label"))
- self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
- self.oj_spinBox = QtGui.QSpinBox(self.groupBox_3)
- self.oj_spinBox.setMinimum(-20)
- self.oj_spinBox.setMaximum(20)
- self.oj_spinBox.setObjectName(_fromUtf8("oj_spinBox"))
- self.gridLayout_2.addWidget(self.oj_spinBox, 0, 1, 1, 1)
- spacerItem1 = QtGui.QSpacerItem(
- 248,
- 20,
- QtGui.QSizePolicy.Expanding,
- QtGui.QSizePolicy.Minimum)
- self.gridLayout_2.addItem(spacerItem1, 0, 2, 1, 3)
- self.label_3 = QtGui.QLabel(self.groupBox_3)
- self.label_3.setObjectName(_fromUtf8("label_3"))
- self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
- self.ob1_radioButton = QtGui.QRadioButton(self.groupBox_3)
- self.ob1_radioButton.setObjectName(_fromUtf8("ob1_radioButton"))
- self.gridLayout_2.addWidget(self.ob1_radioButton, 1, 2, 1, 1)
- self.ob2_radioButton = QtGui.QRadioButton(self.groupBox_3)
- self.ob2_radioButton.setObjectName(_fromUtf8("ob2_radioButton"))
- self.gridLayout_2.addWidget(self.ob2_radioButton, 1, 3, 1, 1)
- self.ob3_radioButton = QtGui.QRadioButton(self.groupBox_3)
- self.ob3_radioButton.setObjectName(_fromUtf8("ob3_radioButton"))
- self.gridLayout_2.addWidget(self.ob3_radioButton, 1, 4, 1, 1)
- self.ob_spinBox = QtGui.QSpinBox(self.groupBox_3)
- self.ob_spinBox.setMaximum(100)
- self.ob_spinBox.setObjectName(_fromUtf8("ob_spinBox"))
- self.gridLayout_2.addWidget(self.ob_spinBox, 1, 1, 1, 1)
- self.verticalLayout_2.addWidget(self.groupBox_3)
- self.groupBox_6 = QtGui.QGroupBox(self.groupBox)
- self.groupBox_6.setObjectName(_fromUtf8("groupBox_6"))
- self.verticalLayout = QtGui.QVBoxLayout(self.groupBox_6)
- self.verticalLayout.setSpacing(2)
- self.verticalLayout.setMargin(2)
- self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
- self.fixed_checkBox = QtGui.QCheckBox(self.groupBox_6)
- self.fixed_checkBox.setObjectName(_fromUtf8("fixed_checkBox"))
- self.verticalLayout.addWidget(self.fixed_checkBox)
- self.removable_checkBox = QtGui.QCheckBox(self.groupBox_6)
- self.removable_checkBox.setObjectName(_fromUtf8("removable_checkBox"))
- self.verticalLayout.addWidget(self.removable_checkBox)
- self.verticalLayout_2.addWidget(self.groupBox_6)
- self.gridLayout_5.addWidget(self.groupBox, 2, 0, 2, 1)
-
- self.retranslateUi(Dialog)
- QtCore.QObject.connect(
- self.buttonBox,
- QtCore.SIGNAL(_fromUtf8("accepted()")),
- Dialog.accept)
- QtCore.QObject.connect(
- self.buttonBox,
- QtCore.SIGNAL(_fromUtf8("rejected()")),
- Dialog.reject)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- Dialog.setWindowTitle(_("Dialog"))
- self.groupBox_5.setTitle(_("Dental History"))
- self.label_2.setText(_("Previous Referral Date"))
- self.ref1_radioButton.setText(_("1st referral"))
- self.ref2_radioButton.setText(_("re - referral"))
- self.tx_checkBox.setText(
- _("I am Willing to carry out simple treatment"))
- self.chart_groupBox.setTitle(_("Teeth With Poor Prognosis"))
- self.groupBox_4.setTitle(_("Relevant Medical History"))
- self.groupBox.setTitle(_("Reason for Referral"))
- self.groupBox_2.setTitle(_("Crowding"))
- self.crowding5_radioButton.setText(_("Severe"))
- self.crowding4_radioButton.setText(_("Moderate"))
- self.crowding3_radioButton.setText(_("Mild"))
- self.crowding2_radioButton.setText(_("None"))
- self.crowding1_radioButton.setText(_("Spaced"))
- self.groupBox_3.setTitle(_("Incisal Relationship"))
- self.label.setText(_("Overjet:"))
- self.oj_spinBox.setSuffix(_("mm"))
- self.label_3.setText(_("Overbite"))
- self.ob1_radioButton.setText(_("Complete"))
- self.ob2_radioButton.setText(_("InComplete"))
- self.ob3_radioButton.setText(_("Traumatic"))
- self.ob_spinBox.setSuffix(_("%"))
- self.groupBox_6.setTitle(_("Patient Motivation"))
- self.fixed_checkBox.setText(_("Fixed Appliance"))
- self.removable_checkBox.setText(_("Removable Applicance"))
-
-
-if __name__ == "__main__":
- import gettext
- gettext.install("openmolar")
- import sys
- app = QtGui.QApplication(sys.argv)
- Dialog = QtGui.QDialog()
- ui = Ui_Dialog()
- ui.setupUi(Dialog)
- Dialog.show()
- sys.exit(app.exec_())
diff --git a/src/openmolar/qt4gui/compiled_uis/Ui_toothProps.py b/src/openmolar/qt4gui/compiled_uis/Ui_toothProps.py
index 377e7e6..2de9cd8 100644
--- a/src/openmolar/qt4gui/compiled_uis/Ui_toothProps.py
+++ b/src/openmolar/qt4gui/compiled_uis/Ui_toothProps.py
@@ -3,8 +3,8 @@
# Form implementation generated from reading ui file '/home/neil/openmolar/openmolar1/src/openmolar/qt-designer/toothProps.ui'
#
-# Created: Sat Dec 14 19:08:51 2013
-# by: PyQt4 UI code generator 4.10.3
+# Created: Tue Oct 7 12:56:25 2014
+# by: PyQt4 UI code generator 4.11.2
#
# WARNING! All changes made in this file will be lost!
@@ -91,6 +91,7 @@ class Ui_Form(object):
self.editframe.setObjectName(_fromUtf8("editframe"))
self.verticalLayout.addWidget(self.editframe)
self.comments_comboBox = QtGui.QComboBox(Form)
+ self.comments_comboBox.setEditable(True)
self.comments_comboBox.setObjectName(_fromUtf8("comments_comboBox"))
self.comments_comboBox.addItem(_fromUtf8(""))
self.comments_comboBox.addItem(_fromUtf8(""))
@@ -103,6 +104,7 @@ class Ui_Form(object):
self.comments_comboBox.addItem(_fromUtf8(""))
self.comments_comboBox.addItem(_fromUtf8(""))
self.comments_comboBox.addItem(_fromUtf8(""))
+ self.comments_comboBox.addItem(_fromUtf8(""))
self.verticalLayout.addWidget(self.comments_comboBox)
self.frame = QtGui.QFrame(Form)
self.frame.setMinimumSize(QtCore.QSize(0, 120))
@@ -212,7 +214,7 @@ class Ui_Form(object):
self.cb_scrollArea.setWidgetResizable(True)
self.cb_scrollArea.setObjectName(_fromUtf8("cb_scrollArea"))
self.scrollAreaWidgetContents = QtGui.QWidget()
- self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 144, 147))
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 144, 149))
self.scrollAreaWidgetContents.setObjectName(
_fromUtf8("scrollAreaWidgetContents"))
self.cb_scrollArea.setWidget(self.scrollAreaWidgetContents)
@@ -225,16 +227,17 @@ class Ui_Form(object):
Form.setWindowTitle(_("Form"))
self.clear_pushButton.setToolTip(_("delete tooth data"))
self.comments_comboBox.setItemText(0, _("ADD COMMENTS"))
- self.comments_comboBox.setItemText(1, _("!KUO"))
- self.comments_comboBox.setItemText(2, _("!Mobile Tooth"))
- self.comments_comboBox.setItemText(3, _("!Early Caries"))
- self.comments_comboBox.setItemText(4, _("!Filling Missing"))
- self.comments_comboBox.setItemText(5, _("!Chipped"))
- self.comments_comboBox.setItemText(6, _("!Cracked"))
- self.comments_comboBox.setItemText(7, _("!Poor Prognosis"))
- self.comments_comboBox.setItemText(8, _("!Extract Soon"))
- self.comments_comboBox.setItemText(9, _("!Implant required"))
- self.comments_comboBox.setItemText(10, _("DELETE COMMENTS"))
+ self.comments_comboBox.setItemText(1, _("KUO"))
+ self.comments_comboBox.setItemText(2, _("Mobile Tooth"))
+ self.comments_comboBox.setItemText(3, _("Early Caries"))
+ self.comments_comboBox.setItemText(4, _("Filling Missing"))
+ self.comments_comboBox.setItemText(5, _("Chipped"))
+ self.comments_comboBox.setItemText(6, _("Cracked"))
+ self.comments_comboBox.setItemText(7, _("Poor Prognosis"))
+ self.comments_comboBox.setItemText(8, _("Extract Soon"))
+ self.comments_comboBox.setItemText(9, _("Sensitive"))
+ self.comments_comboBox.setItemText(10, _("Non Vital"))
+ self.comments_comboBox.setItemText(11, _("DELETE ALL COMMENTS"))
self.am_pushButton.setText(_("AM"))
self.co_pushButton.setText(_("CO"))
self.gl_pushButton.setText(_("GL"))
diff --git a/src/openmolar/qt4gui/customwidgets/completer_textedit.py b/src/openmolar/qt4gui/customwidgets/completer_textedit.py
new file mode 100644
index 0000000..6154e7c
--- /dev/null
+++ b/src/openmolar/qt4gui/customwidgets/completer_textedit.py
@@ -0,0 +1,122 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+from PyQt4 import QtGui, QtCore
+
+
+class CompletionTextEdit(QtGui.QTextEdit):
+
+ def __init__(self, parent=None):
+ QtGui.QTextEdit.__init__(self, parent)
+ self.setMinimumWidth(400)
+ self.completer = None
+ self.setTabChangesFocus(True)
+
+ def set_wordset(self, words):
+ if self.completer:
+ self.completer.activated.disconnect(self.insertCompletion)
+
+ completer = QtGui.QCompleter(sorted(words), self)
+ completer.setWidget(self)
+ completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
+ completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
+ self.completer = completer
+ self.completer.activated.connect(self.insertCompletion)
+
+ def insertCompletion(self, completion):
+ tc = self.textCursor()
+ tc.movePosition(tc.StartOfWord)
+ for i in range(self.completer.completionPrefix().length()):
+ tc.deleteChar()
+ tc.insertText(completion)
+
+ def textUnderCursor(self):
+ tc = self.textCursor()
+ tc.select(QtGui.QTextCursor.WordUnderCursor)
+ return tc.selectedText()
+
+ def focusInEvent(self, event):
+ if self.completer:
+ self.completer.setWidget(self)
+ QtGui.QTextEdit.focusInEvent(self, event)
+
+ def keyPressEvent(self, event):
+ if self.completer is None:
+ QtGui.QTextEdit.keyPressEvent(self, event)
+ return
+ if self.completer and self.completer.popup().isVisible():
+ if event.key() in (
+ QtCore.Qt.Key_Enter,
+ QtCore.Qt.Key_Return,
+ QtCore.Qt.Key_Escape,
+ QtCore.Qt.Key_Tab,
+ QtCore.Qt.Key_Backtab):
+ event.ignore()
+ return
+
+ # has ctrl-E been pressed??
+ isShortcut = (event.modifiers() == QtCore.Qt.ControlModifier and
+ event.key() == QtCore.Qt.Key_E)
+ if not isShortcut:
+ QtGui.QTextEdit.keyPressEvent(self, event)
+ # return
+
+ # ctrl or shift key on it's own??
+ ctrlOrShift = event.modifiers() in (QtCore.Qt.ControlModifier,
+ QtCore.Qt.ShiftModifier)
+ if ctrlOrShift and event.text().isEmpty():
+ # ctrl or shift key on it's own
+ return
+
+ eow = QtCore.QString(
+ "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=") # end of word
+
+ hasModifier = ((event.modifiers() != QtCore.Qt.NoModifier) and
+ not ctrlOrShift)
+
+ completionPrefix = self.textUnderCursor()
+
+ if (not isShortcut and (hasModifier or event.text().isEmpty() or
+ completionPrefix.length() < 2 or
+ eow.contains(event.text().right(1)))):
+ self.completer.popup().hide()
+ return
+
+ if (completionPrefix != self.completer.completionPrefix()):
+ self.completer.setCompletionPrefix(completionPrefix)
+ popup = self.completer.popup()
+ popup.setCurrentIndex(
+ self.completer.completionModel().index(0, 0))
+
+ cr = self.cursorRect()
+ cr.setWidth(self.completer.popup().sizeHintForColumn(0)
+ + self.completer.popup().verticalScrollBar().sizeHint().width())
+ self.completer.complete(cr) # popup it up!
+
+if __name__ == "__main__":
+ app = QtGui.QApplication([])
+ te = CompletionTextEdit()
+ te.set_wordset(["Nevertheless", "Neverending", "Never"])
+ te.show()
+ app.exec_()
diff --git a/src/openmolar/qt4gui/customwidgets/toothProps.py b/src/openmolar/qt4gui/customwidgets/toothProps.py
index 913915a..8a35ffa 100644
--- a/src/openmolar/qt4gui/customwidgets/toothProps.py
+++ b/src/openmolar/qt4gui/customwidgets/toothProps.py
@@ -76,8 +76,11 @@ class chartLineEdit(QtGui.QLineEdit):
deletes all props
'''
new_props = []
+ found = False
for ex_prop in self.propListFromText():
- if ex_prop.upper() != prop:
+ if not found and ex_prop.upper() == prop.upper():
+ found = True
+ else:
new_props.append(ex_prop)
self.updateFromPropList(new_props)
@@ -377,12 +380,12 @@ class ToothPropertyEditingWidget(QtGui.QWidget, Ui_toothProps.Ui_Form):
'''
comments combobox has been nav'd
'''
- if arg == _("ADD COMMENTS"):
+ if _("ADD COMMENTS") in arg:
return
- elif arg == _("DELETE COMMENTS"):
+ elif arg == _("DELETE ALL COMMENTS"):
self.lineEdit.deleteComments()
else:
- newComment = "%s" % arg.replace(" ", "_")
+ newComment = "!%s" % arg.replace(" ", "_")
self.lineEdit.addItem(newComment)
self.comments_comboBox.setCurrentIndex(0)
diff --git a/src/openmolar/qt4gui/dialogs/advanced_record_management_dialog.py b/src/openmolar/qt4gui/dialogs/advanced_record_management_dialog.py
index 95b5fcf..437c2dc 100644
--- a/src/openmolar/qt4gui/dialogs/advanced_record_management_dialog.py
+++ b/src/openmolar/qt4gui/dialogs/advanced_record_management_dialog.py
@@ -22,7 +22,6 @@
# # # #
# ############################################################################ #
-
import logging
from PyQt4 import QtGui, QtCore
@@ -64,6 +63,8 @@ class AdvancedRecordManagementDialog(BaseDialog):
self.pt.pd10,
self.pt.billdate)
+ self.ui.money0_spinBox.setEnabled(False)
+ self.ui.money1_spinBox.setEnabled(False)
self.check_before_reject_if_dirty = True
def sizeHint(self):
diff --git a/src/openmolar/qt4gui/dialogs/alter_todays_notes.py b/src/openmolar/qt4gui/dialogs/alter_todays_notes.py
index dc62686..4af2b9f 100644
--- a/src/openmolar/qt4gui/dialogs/alter_todays_notes.py
+++ b/src/openmolar/qt4gui/dialogs/alter_todays_notes.py
@@ -117,7 +117,7 @@ class AlterTodaysNotesDialog(BaseDialog):
self.enableApply()
def apply_changed(self):
- lines = (unicode(self.text_edit.toPlainText())).split("\n")
+ lines = unicode(self.text_edit.toPlainText()).strip(" \n)").split("\n")
short_lines = []
for note in lines:
while len(note) > 79:
@@ -132,7 +132,7 @@ class AlterTodaysNotesDialog(BaseDialog):
#--ok, no option (unlikely to happen though)
short_lines.append(note[:pos])
note = note[pos + 1:]
- short_lines.append(note + "\n")
+ short_lines.append(note.strip(" \n") + "\n")
values = []
i = 0
@@ -151,8 +151,10 @@ class AlterTodaysNotesDialog(BaseDialog):
cursor.close()
if len(short_lines) > i:
- patient_write_changes.toNotes(self.sno,
- [("newNOTE", n) for n in short_lines[i:]])
+ patient_write_changes.toNotes(
+ self.sno,
+ [("newNOTE", n) for n in short_lines[i:]]
+ )
def exec_(self):
if BaseDialog.exec_(self):
diff --git a/src/openmolar/qt4gui/dialogs/auto_address_dialog.py b/src/openmolar/qt4gui/dialogs/auto_address_dialog.py
index 746d6ae..2b2166a 100644
--- a/src/openmolar/qt4gui/dialogs/auto_address_dialog.py
+++ b/src/openmolar/qt4gui/dialogs/auto_address_dialog.py
@@ -143,11 +143,13 @@ class AutoAddressDialog(BaseDialog):
if self.tel1_cb.isChecked():
self.om_gui.ui.tel1Edit.setText(self.tel1_le.text())
+ self.om_gui.advise(_("Address changes applied"), 1)
+
def sizeHint(self):
return QtCore.QSize(600, 350)
def exec_(self):
- if localsettings.LAST_ADDRESS == ("",) * 8:
+ if localsettings.LAST_ADDRESS == localsettings.BLANK_ADDRESS:
self.om_gui.advise(_("No previous address details found"), 1)
elif BaseDialog.exec_(self):
return True
diff --git a/src/openmolar/qt4gui/dialogs/child_smile_dialog.py b/src/openmolar/qt4gui/dialogs/child_smile_dialog.py
index 998185b..8413b06 100644
--- a/src/openmolar/qt4gui/dialogs/child_smile_dialog.py
+++ b/src/openmolar/qt4gui/dialogs/child_smile_dialog.py
@@ -63,6 +63,13 @@ EXAMPLE_RESULT = '''
</html>
'''
+HEADERS = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
+ 'Accept-Encoding': 'none',
+ 'Accept-Language': 'en-US,en;q=0.8',
+ 'Connection': 'keep-alive'}
+
TODAYS_LOOKUPS = {} # {"IV1 1PP": "SIMD Area: 1"}
@@ -151,12 +158,11 @@ class ChildSmileDialog(BaseDialog):
try:
QtGui.QApplication.instance().setOverrideCursor(
QtCore.Qt.WaitCursor)
- req = urllib2.Request(url)
- response = urllib2.urlopen(req, timeout=10)
+ req = urllib2.Request(url, headers=HEADERS)
+ response = urllib2.urlopen(req, timeout=20)
result = response.read()
self.result = self._parse_result(result)
except urllib2.URLError as exc:
- raise socket.timeout(exc)
LOGGER.error("url error polling NHS website?")
self.result = _("Error polling website")
except socket.timeout as e:
@@ -190,6 +196,7 @@ class ChildSmileDialog(BaseDialog):
4, 1, 5)
if not result:
self.reject()
+ self.result += " - Manually entered SIMD of %d" % simd
return simd
@property
diff --git a/src/openmolar/qt4gui/dialogs/clinician_select_dialog.py b/src/openmolar/qt4gui/dialogs/clinician_select_dialog.py
index 3fd4831..8768205 100644
--- a/src/openmolar/qt4gui/dialogs/clinician_select_dialog.py
+++ b/src/openmolar/qt4gui/dialogs/clinician_select_dialog.py
@@ -40,8 +40,8 @@ class ClinicianSelectDialog(QtGui.QDialog):
self.listwidget.setSelectionMode(
QtGui.QAbstractItemView.SingleSelection)
- clinicians = [_("NONE")] + localsettings.activedents + \
- localsettings.activehygs
+ clinicians = [_("NONE")] + list(localsettings.activedents) + \
+ list(localsettings.activehygs)
self.listwidget.addItems(clinicians)
try:
@@ -76,15 +76,17 @@ class ClinicianSelectDialog(QtGui.QDialog):
localsettings.clinicianNo = localsettings.ops_reverse.get(
chosen, 0)
curr_operator = localsettings.operator.split("/")
- u2 = curr_operator[-1]
+ u2 = curr_operator[0]
if u2 == chosen:
u2 = ""
if u2:
- input = QtGui.QMessageBox.question(self, _("Confirm"),
- _("Set assistant as") +
- " %s?" % u2,
- QtGui.QMessageBox.No | QtGui.QMessageBox.Yes,
- QtGui.QMessageBox.Yes)
+ input = QtGui.QMessageBox.question(
+ self,
+ _("Confirm"),
+ _("Set Clinician as") +
+ " %s?" % chosen,
+ QtGui.QMessageBox.No | QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.Yes)
if input == QtGui.QMessageBox.No:
u2 = ""
localsettings.setOperator(chosen, u2)
diff --git a/src/openmolar/qt4gui/dialogs/correspondence_dialog.py b/src/openmolar/qt4gui/dialogs/correspondence_dialog.py
new file mode 100644
index 0000000..492a9cd
--- /dev/null
+++ b/src/openmolar/qt4gui/dialogs/correspondence_dialog.py
@@ -0,0 +1,143 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+import logging
+import re
+
+from PyQt4 import QtGui, QtCore
+
+from openmolar.qt4gui.dialogs.base_dialogs import BaseDialog
+from openmolar.dbtools import standard_letter
+
+LOGGER = logging.getLogger("openmolar")
+
+
+class CorrespondenceDialog(BaseDialog):
+ LETTERS = None
+
+ def __init__(self, html, patient=None, preformatted=True, parent=None):
+ BaseDialog.__init__(self, parent, remove_stretch=True)
+
+ self.pt = patient
+ self.text_edit = QtGui.QTextEdit()
+ self.orig_html = html
+ self.text_edit.setHtml(html)
+ self.orig_qhtml = self.text
+ self.insertWidget(self.text_edit)
+
+ if preformatted:
+ self.combo_box = QtGui.QComboBox()
+ self.combo_box.addItem(_("Blank Letter"))
+ QtCore.QTimer.singleShot(100, self.load_preformats)
+ self.insertWidget(self.combo_box)
+
+ self.enableApply()
+
+ def advise(self, message):
+ QtGui.QMessageBox.information(self, _("message"), message)
+
+ def sizeHint(self):
+ return QtCore.QSize(600, 600)
+
+ def showEvent(self, event):
+ self.text_edit.setFocus()
+
+ def replace_placeholders(self, text):
+ try:
+ text = text.replace("{{NAME}}", self.pt.name)
+ text = text.replace("{{SERIALNO}}", str(self.pt.serialno))
+ except AttributeError:
+ LOGGER.warning("couldn't replace placeholders")
+ pass
+ return text
+
+ def load_preformats(self):
+ if self.LETTERS is None:
+ blank_letter = standard_letter.StandardLetter(
+ _("Blank Letter"),
+ "<br />" * 9,
+ "")
+
+ LOGGER.info("loading preformatted letters")
+ CorrespondenceDialog.LETTERS = {
+ blank_letter.description: blank_letter}
+
+ for letter in standard_letter.get_standard_letters():
+ CorrespondenceDialog.LETTERS[letter.description] = letter
+
+ for key, letter in self.LETTERS.iteritems():
+ if key != _("Blank Letter"):
+ self.combo_box.addItem(letter.description)
+ self.combo_box.currentIndexChanged.connect(
+ self.preformed_letter_selected)
+
+ def preformed_letter_selected(self, i):
+ LOGGER.debug("selecting preformed letter %s", i)
+ selected = unicode(self.combo_box.currentText())
+ if self.has_edits and QtGui.QMessageBox.question(
+ self,
+ _("Confirm"),
+ "%s %s" % (
+ _("Abandon changes and convert to letter type"),
+ selected),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
+ ) == QtGui.QMessageBox.No:
+ return
+
+ letter = self.LETTERS[selected]
+ new_body = "<!-- letter body -->\n%s\n<!-- end of letter body -->" % \
+ letter.text
+ new_footer = "<!-- footer -->\n%s\n<!-- end of footer -->" % \
+ letter.footer
+
+ compiled = re.compile(
+ r"<!-- letter body -->(.*)<!-- end of letter body -->", re.DOTALL)
+ new_text = re.sub(compiled, new_body, self.orig_html)
+
+ compiled = re.compile(
+ r"<!-- footer -->(.*)<!-- end of footer -->", re.DOTALL)
+ new_text = re.sub(compiled, new_footer, new_text)
+
+ new_text = self.replace_placeholders(new_text)
+
+ self.text_edit.setHtml(new_text)
+ self.orig_qhtml = self.text
+
+ @property
+ def has_edits(self):
+ return self.text != self.orig_qhtml
+
+ @property
+ def text(self):
+ return unicode(self.text_edit.toHtml())
+
+ @property
+ def letter_description(self):
+ return unicode(self.combo_box.currentText())
+
+if __name__ == "__main__":
+ app = QtGui.QApplication([])
+ LOGGER.setLevel(logging.DEBUG)
+ dl = CorrespondenceDialog(standard_letter._test())
+ dl.exec_()
diff --git a/src/openmolar/qt4gui/dialogs/dialog_collection.py b/src/openmolar/qt4gui/dialogs/dialog_collection.py
index ad52134..b0d54e8 100644
--- a/src/openmolar/qt4gui/dialogs/dialog_collection.py
+++ b/src/openmolar/qt4gui/dialogs/dialog_collection.py
@@ -35,7 +35,7 @@ from openmolar.qt4gui.dialogs.assistant_select_dialog import AssistantSelectDial
from openmolar.qt4gui.dialogs.clinician_select_dialog import ClinicianSelectDialog
from openmolar.qt4gui.dialogs.duplicate_receipt_dialog import DuplicateReceiptDialog
from openmolar.qt4gui.dialogs.save_discard_cancel import SaveDiscardCancelDialog
-from openmolar.qt4gui.dialogs.med_notes_dialog import MedNotesDialog
+from openmolar.qt4gui.dialogs.medical_history_dialog import MedicalHistoryDialog
from openmolar.qt4gui.dialogs.choose_tooth_dialog import ChooseToothDialog
from openmolar.qt4gui.dialogs.exam_wizard import ExamWizard
from openmolar.qt4gui.dialogs.hygTreatWizard import HygTreatWizard
@@ -67,6 +67,8 @@ from openmolar.qt4gui.dialogs.add_clinician_dialog import AddClinicianDialog
from openmolar.qt4gui.dialogs.initial_check_dialog import InitialCheckDialog
from openmolar.qt4gui.dialogs.edit_practice_dialog import EditPracticeDialog
from openmolar.qt4gui.dialogs.advanced_record_management_dialog import AdvancedRecordManagementDialog
+from openmolar.qt4gui.dialogs.correspondence_dialog import CorrespondenceDialog
+from openmolar.qt4gui.dialogs.edit_standard_letters_dialog import EditStandardLettersDialog
__all__ = ['AccountSeverityDialog',
'AddClinicianDialog',
@@ -80,6 +82,7 @@ __all__ = ['AccountSeverityDialog',
'ChildSmileDialog',
'ChooseToothDialog',
'ClinicianSelectDialog',
+ 'CorrespondenceDialog',
'CourseConsistencyDialog',
'CourseEditDialog',
'CourseMergeDialog',
@@ -91,6 +94,7 @@ __all__ = ['AccountSeverityDialog',
'EditPracticeDialog',
'EditTreatmentDialog',
'EditReferralCentresDialog',
+ 'EditStandardLettersDialog',
'EstimateEditDialog',
'ExamWizard',
'FamilyManageDialog',
@@ -100,7 +104,8 @@ __all__ = ['AccountSeverityDialog',
'InitialCheckDialog',
'LoadRelativesDialog',
'LoginDialog',
- 'MedNotesDialog',
+ #'MedNotesDialog',
+ 'MedicalHistoryDialog',
'NHSFormsConfigDialog',
'ResetSupervisorPasswordDialog',
'RecallDialog',
diff --git a/src/openmolar/qt4gui/dialogs/edit_standard_letters_dialog.py b/src/openmolar/qt4gui/dialogs/edit_standard_letters_dialog.py
new file mode 100644
index 0000000..4e579a2
--- /dev/null
+++ b/src/openmolar/qt4gui/dialogs/edit_standard_letters_dialog.py
@@ -0,0 +1,307 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+import logging
+
+from PyQt4 import QtGui, QtCore, Qsci
+
+from openmolar.dbtools import standard_letter
+from openmolar.qt4gui.dialogs.base_dialogs import BaseDialog
+
+LOGGER = logging.getLogger("openmolar")
+
+
+class ListModel(QtCore.QAbstractListModel):
+
+ '''
+ A simple model to provide an index for the dialog
+ '''
+
+ def __init__(self, parent=None):
+ QtCore.QAbstractListModel.__init__(self, parent)
+ self.labels = []
+
+ def rowCount(self, parent=QtCore.QModelIndex()):
+ return len(self.labels)
+
+ def data(self, index, role):
+ if not index.isValid():
+ pass
+ elif role == QtCore.Qt.DisplayRole:
+ return self.labels[index.row()]
+ elif role == QtCore.Qt.DecorationRole:
+ return QtGui.QIcon(":icons/pencil.png")
+
+ def clear(self):
+ self.beginResetModel()
+ self.labels = []
+ self.endResetModel()
+
+ def add_item(self, label):
+ self.beginResetModel()
+ self.labels.append(label)
+ self.endResetModel()
+
+
+class EditStandardLettersDialog(BaseDialog):
+
+ def __init__(self, parent=None):
+ BaseDialog.__init__(self, parent, remove_stretch=True)
+ message = _("Edit Standard Letters")
+ self.setWindowTitle(message)
+
+ self._standard_letters = None
+ self.deleted_letters = []
+
+ header_label = QtGui.QLabel("<b>%s</b>" % message)
+
+ self.list_model = ListModel()
+
+ self.list_view = QtGui.QListView()
+ self.list_view.setModel(self.list_model)
+
+ icon = QtGui.QIcon(":/eraser.png")
+ delete_but = QtGui.QPushButton(icon, "")
+ delete_but.setToolTip(_("Delete the currently selected letter"))
+ delete_but.setMaximumWidth(80)
+
+ icon = QtGui.QIcon(":/add_user.png")
+ add_but = QtGui.QPushButton(icon, "")
+ add_but.setToolTip(_("Add a New Letter"))
+ add_but.setMaximumWidth(80)
+
+ left_frame = QtGui.QFrame()
+ layout = QtGui.QGridLayout(left_frame)
+ layout.setMargin(0)
+ layout.addWidget(self.list_view, 0, 0, 1, 3)
+ layout.addWidget(delete_but, 1, 0)
+ layout.addWidget(add_but, 1, 1)
+ left_frame.setMaximumWidth(250)
+
+ right_frame = QtGui.QFrame()
+ layout = QtGui.QFormLayout(right_frame)
+ layout.setMargin(0)
+ self.description_line_edit = QtGui.QLineEdit()
+ self.text_edit = Qsci.QsciScintilla()
+ self.text_edit.setLexer(Qsci.QsciLexerHTML())
+ self.footer_text_edit = Qsci.QsciScintilla()
+ self.footer_text_edit.setLexer(Qsci.QsciLexerHTML())
+
+ layout.addRow(_("Desctription"), self.description_line_edit)
+ layout.addRow(_("Body Text"), self.text_edit)
+ layout.addRow(_("Footer"), self.footer_text_edit)
+
+ splitter = QtGui.QSplitter()
+ splitter.addWidget(left_frame)
+ splitter.addWidget(right_frame)
+ splitter.setSizes([1, 10])
+ self.insertWidget(header_label)
+ self.insertWidget(splitter)
+
+ self.list_view.pressed.connect(self.show_data)
+
+ self.cancel_but.setText(_("Close"))
+ self.apply_but.setText(_("Apply Changes"))
+
+ self.set_check_on_cancel(True)
+ self.signals()
+ add_but.clicked.connect(self.add_letter)
+ delete_but.clicked.connect(self.remove_letter)
+
+ self.orig_data = []
+ QtCore.QTimer.singleShot(100, self.load_existing)
+
+ def sizeHint(self):
+ return QtCore.QSize(800, 600)
+
+ def signals(self, connect=True):
+ for signal in (
+ self.description_line_edit.editingFinished,
+ self.text_edit.textChanged,
+ self.footer_text_edit.textChanged
+ ):
+ if connect:
+ signal.connect(self.update_letter)
+ else:
+ signal.disconnect(self.update_letter)
+
+ @property
+ def standard_letters(self):
+ if self._standard_letters is None:
+ self._standard_letters = []
+ for letter in standard_letter.get_standard_letters():
+ self._standard_letters.append(letter)
+ self.orig_data.append(str(letter))
+ return self._standard_letters
+
+ @property
+ def existing_descriptions(self):
+ for letter in self.standard_letters:
+ yield letter.description
+
+ def load_existing(self, row=0):
+ if self.standard_letters == []:
+ return
+ self.signals(False)
+ self.list_model.clear()
+ for std_letter in self.standard_letters:
+ if std_letter not in self.deleted_letters:
+ self.list_model.add_item(std_letter.description)
+ index = self.list_model.createIndex(row, 0)
+ self.list_view.setCurrentIndex(index)
+ self.signals()
+ self.show_data(index)
+
+ def show_data(self, index):
+ self.signals(False)
+ letter = self.current_letter
+ self.description_line_edit.setText(letter.description)
+ self.text_edit.setText(letter.text)
+ self.footer_text_edit.setText(letter.footer)
+
+ self.signals()
+
+ @property
+ def current_row(self):
+ return self.list_view.currentIndex().row()
+
+ @property
+ def current_letter(self):
+ i = -1
+ for std_letter in self.standard_letters:
+ if std_letter not in self.deleted_letters:
+ i += 1
+ if i == self.current_row:
+ return std_letter
+
+ @property
+ def description(self):
+ '''
+ return the current description text
+ '''
+ return self.description_line_edit.text()
+
+ @property
+ def body_text(self):
+ '''
+ return the current body text
+ '''
+ return unicode(self.text_edit.text())
+
+ @property
+ def footer_text(self):
+ '''
+ return the current footer text
+ '''
+ return unicode(self.footer_text_edit.text())
+
+ def add_letter(self, triggered=None, name=""):
+ LOGGER.debug("add_letter")
+ name, result = QtGui.QInputDialog.getText(
+ self,
+ _("Input Required"),
+ _("Please enter a unique descriptive name for this letter"),
+ text=name
+ )
+ if not result or name == "":
+ return
+ if name in self.existing_descriptions:
+ QtGui.QMessageBox.warning(self, _("error"),
+ _("this name is already in use")
+ )
+ self.add_letter(name=name)
+ return
+ letter = standard_letter.StandardLetter(
+ name,
+ "<br />" * 4,
+ "<br />" * 4)
+ self.standard_letters.append(letter)
+ rowno = len(self.standard_letters) - len(self.deleted_letters) - 1
+ self.load_existing(rowno)
+ self.check_for_changes()
+
+ def remove_letter(self):
+ if len(self.standard_letters) < 2:
+ QtGui.QMessageBox.warning(
+ self,
+ _("Warning"),
+ _(
+ "You should have at least one standard letter in the database")
+ )
+ return
+ self.deleted_letters.append(self.current_letter)
+ self.load_existing()
+ self.check_for_changes()
+
+ def update_letter(self):
+ letter = standard_letter.StandardLetter(self.description,
+ self.body_text,
+ self.footer_text
+ )
+ self._standard_letters[self.current_row] = letter
+
+ if self.sender() == self.description_line_edit:
+ self.description_edited()
+
+ self.check_for_changes()
+
+ def check_for_changes(self):
+
+ if self.deleted_letters or self.new_letters:
+ self.dirty = True
+ else:
+ for i, letter in enumerate(self.standard_letters):
+ if self.orig_data[i] != str(letter):
+ self.dirty = True
+ break
+ self.enableApply(self.dirty)
+
+ def description_edited(self):
+ rowno = self.current_row
+ self.load_existing(rowno)
+
+ def new_letters(self):
+ return self.standard_letters[len(self.orig_data):]
+
+ def updated_letters(self):
+ for i in range(len(self.orig_data)):
+ letter = self.standard_letters[i]
+ if (self.orig_data[i] != str(letter) and
+ letter not in self.deleted_letters):
+ yield letter
+
+ def exec_(self):
+ if BaseDialog.exec_(self):
+ standard_letter.insert_letters(self.updated_letters())
+ standard_letter.insert_letters(self.new_letters())
+ standard_letter.delete_letters(self.deleted_letters)
+ return True
+ return False
+
+if __name__ == "__main__":
+ LOGGER.setLevel(logging.DEBUG)
+ app = QtGui.QApplication([])
+
+ dl = EditStandardLettersDialog()
+ dl.exec_()
diff --git a/src/openmolar/qt4gui/dialogs/find_patient_dialog.py b/src/openmolar/qt4gui/dialogs/find_patient_dialog.py
index 7cf041e..1bc72cc 100644
--- a/src/openmolar/qt4gui/dialogs/find_patient_dialog.py
+++ b/src/openmolar/qt4gui/dialogs/find_patient_dialog.py
@@ -104,6 +104,7 @@ class FindPatientDialog(QtGui.QDialog, Ui_patient_finder.Ui_Dialog):
class FinalChoiceDialog(ExtendableDialog):
chosen_sno = None
+ FILTER = True
def __init__(self, candidates, parent=None):
ExtendableDialog.__init__(self, parent, remove_stretch=True)
@@ -113,30 +114,53 @@ class FinalChoiceDialog(ExtendableDialog):
QtGui.QAbstractItemView.SelectRows)
self.insertWidget(self.table_widget)
- headers = (_('Serialno'),
- _('Status'),
- _('Title'),
- _('Forename'),
- _('Surname'),
- _('Birth Date'),
- _('Address Line 1'),
- _('Address Line 2'),
- _('Town'),
- _('POSTCODE'),
- _('Tel1'),
- _('Tel2'),
- _('Mobile')
- )
+ self.headers = (_('Serialno'),
+ _('Status'),
+ _('Title'),
+ _('Forename'),
+ _('Surname'),
+ _('Birth Date'),
+ _('Address Line 1'),
+ _('Address Line 2'),
+ _('Town'),
+ _('POSTCODE'),
+ _('Tel1'),
+ _('Tel2'),
+ _('Mobile')
+ )
+ self._candidates = candidates
+ self.hidden_count = 0
+ self.load_candidates()
+ self.table_widget.itemDoubleClicked.connect(self.accept)
+ self.enableApply(True)
+ self.apply_but.setText(_("Load the Selected Patient"))
+ self.setMinimumWidth(
+ QtGui.QApplication.desktop().screenGeometry().width() - 20)
+ def _screened_candidates(self):
+ self.hidden_count = 0
+ for candidate in self._candidates:
+ if candidate[1] == "":
+ yield candidate
+ else:
+ self.hidden_count += 1
+
+ @property
+ def candidates(self):
+ if not self.FILTER:
+ return self._candidates
+ return list(self._screened_candidates())
+
+ def load_candidates(self):
self.table_widget.clear()
self.table_widget.setSortingEnabled(False)
- self.table_widget.setRowCount(len(candidates))
- self.table_widget.setColumnCount(len(headers))
- self.table_widget.setHorizontalHeaderLabels(headers)
+ self.table_widget.setRowCount(len(self.candidates))
+ self.table_widget.setColumnCount(len(self.headers))
+ self.table_widget.setHorizontalHeaderLabels(self.headers)
self.table_widget.verticalHeader().hide()
self.table_widget.horizontalHeader().setStretchLastSection(True)
- for row, candidate in enumerate(candidates):
+ for row, candidate in enumerate(self.candidates):
for col, attr in enumerate(candidate):
if isinstance(attr, datetime.date):
item = QtGui.QTableWidgetItem(
@@ -145,15 +169,10 @@ class FinalChoiceDialog(ExtendableDialog):
item = QtGui.QTableWidgetItem(str(attr))
self.table_widget.setItem(row, col, item)
- self.table_widget.setCurrentCell(0, 1)
self.table_widget.setSortingEnabled(True)
self.table_widget.sortItems(4)
-
- self.table_widget.itemDoubleClicked.connect(self.accept)
- self.enableApply(True)
- self.apply_but.setText(_("Load the Selected Patient"))
- self.setMinimumWidth(
- QtGui.QApplication.desktop().screenGeometry().width()-20)
+ self.table_widget.setCurrentCell(0, 1)
+ self.set_more_but_text()
def sizeHint(self):
return QtCore.QSize(self.minimumWidth(), 400)
@@ -165,6 +184,30 @@ class FinalChoiceDialog(ExtendableDialog):
col_width = widths[col] * self.width() / sum_widths
self.table_widget.setColumnWidth(col, col_width)
+ def set_more_but_text(self):
+ if self.FILTER:
+ self.more_but.setText(
+ "%s (%d %s)" % (
+ _("Include ALL Patients"),
+ self.hidden_count,
+ _("are hidden")
+ ))
+ self.more_but.setStyleSheet("color:red")
+ else:
+ self.more_but.setText(_("Show only active Patients"))
+ self.more_but.setStyleSheet("")
+ self.more_but.setChecked(self.FILTER)
+
+ def _clicked(self, but):
+ '''
+ overwrite :doc:`ExtendableDialog` _clicked
+ '''
+ if but == self.more_but:
+ FinalChoiceDialog.FILTER = not self.FILTER
+ self.load_candidates()
+ return
+ ExtendableDialog._clicked(self, but)
+
def exec_(self):
if QtGui.QDialog.exec_(self):
row = self.table_widget.currentRow()
diff --git a/src/openmolar/qt4gui/dialogs/med_notes_dialog.py b/src/openmolar/qt4gui/dialogs/med_notes_dialog.py
deleted file mode 100644
index c1d4d31..0000000
--- a/src/openmolar/qt4gui/dialogs/med_notes_dialog.py
+++ /dev/null
@@ -1,145 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# ############################################################################ #
-# # # #
-# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
-# # # #
-# # This file is part of OpenMolar. # #
-# # # #
-# # OpenMolar is free software: you can redistribute it and/or modify # #
-# # it under the terms of the GNU General Public License as published by # #
-# # the Free Software Foundation, either version 3 of the License, or # #
-# # (at your option) any later version. # #
-# # # #
-# # OpenMolar is distributed in the hope that it will be useful, # #
-# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
-# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
-# # GNU General Public License for more details. # #
-# # # #
-# # You should have received a copy of the GNU General Public License # #
-# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
-# # # #
-# ############################################################################ #
-
-import logging
-import datetime
-
-from PyQt4 import QtGui, QtCore
-
-from openmolar.settings import localsettings
-from openmolar.qt4gui.compiled_uis import Ui_medhist
-from openmolar.dbtools import updateMH
-
-LOGGER = logging.getLogger("openmolar")
-
-
-class MedNotesDialog(QtGui.QDialog, Ui_medhist.Ui_Dialog):
-
- def __init__(self, pt, parent=None):
- QtGui.QDialog.__init__(self)
- self.setupUi(self)
- self.checked_pushButton.clicked.connect(self.update_date)
- self.alert = False
- self.chkdate = None
-
- self.pt = pt
- self.data = pt.MH
-
- self.load_data()
- self.set_date()
- self.checkBox.setChecked(self.alert)
-
- def update_date(self):
- self.dateEdit.setDate(datetime.date.today())
- self.dateEdit.show()
- self.date_label.show()
-
- def load_data(self):
- if self.data is None:
- return
- for i, lineEdit in enumerate((
- self.doctor_lineEdit,
- self.doctorAddy_lineEdit,
- self.curMeds_lineEdit,
- self.pastMeds_lineEdit,
- self.allergies_lineEdit,
- self.heart_lineEdit,
- self.lungs_lineEdit,
- self.liver_lineEdit,
- self.bleeding_lineEdit,
- self.kidneys_lineEdit,
- self.anaesthetic_lineEdit,
- self.other_lineEdit)):
- lineEdit.setText(self.data[i])
-
- self.alert = self.data[12]
- self.chkdate = self.data[13]
-
- def set_date(self):
- if self.chkdate:
- self.dateEdit.setDate(self.chkdate)
- else:
- self.date_label.hide()
- self.dateEdit.hide()
-
- def exec_(self):
- if not QtGui.QDialog.exec_(self):
- return False
- newdata = []
- for lineEdit in (
- self.doctor_lineEdit,
- self.doctorAddy_lineEdit,
- self.curMeds_lineEdit,
- self.pastMeds_lineEdit,
- self.allergies_lineEdit,
- self.heart_lineEdit,
- self.lungs_lineEdit,
- self.liver_lineEdit,
- self.bleeding_lineEdit,
- self.kidneys_lineEdit,
- self.anaesthetic_lineEdit,
- self.other_lineEdit
- ):
- newdata.append(unicode(lineEdit.text().toUtf8()))
-
- newdata.append(self.checkBox.isChecked())
- chkdate = self.dateEdit.date().toPyDate()
- if chkdate != datetime.date(1900, 1, 1):
- newdata.append(chkdate)
- else:
- newdata.append(None)
-
- self.result = tuple(newdata)
-
- return self.data != self.result
-
- def apply(self):
- LOGGER.info("applying new MH data")
- updateMH.write(self.pt.serialno, self.result)
- self.pt.MH = self.result
- self.pt.MEDALERT = self.result[12]
-
- self.pt.addHiddenNote("mednotes")
-
- mnhistChanges = []
- if self.data is not None:
- for i, orig in enumerate(self.data[:11]):
- if orig and orig != self.result[i]:
- mnhistChanges.append((i + 140, orig))
- if mnhistChanges != []:
- updateMH.writeHist(self.pt.serialno, mnhistChanges)
- self.pt.addHiddenNote("mednotes", "saved previous MH")
- return True
-
-
-if __name__ == "__main__":
- app = QtGui.QApplication([])
- from openmolar.dbtools import patient_class
- try:
- pt = patient_class.patient(1)
- dl = MedNotesDialog(pt)
- if dl.exec_():
- dl.apply()
- except localsettings.PatientNotFoundError:
- LOGGER.exception("no such pt in THIS database")
diff --git a/src/openmolar/qt4gui/dialogs/medical_history_dialog.py b/src/openmolar/qt4gui/dialogs/medical_history_dialog.py
new file mode 100644
index 0000000..9a8fb49
--- /dev/null
+++ b/src/openmolar/qt4gui/dialogs/medical_history_dialog.py
@@ -0,0 +1,425 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+import logging
+
+from PyQt4 import QtGui, QtCore
+
+from openmolar.settings import localsettings
+from openmolar.dbtools import medhist
+from openmolar.qt4gui.dialogs.base_dialogs import BaseDialog
+from openmolar.qt4gui.customwidgets.completer_textedit import CompletionTextEdit
+LOGGER = logging.getLogger("openmolar")
+
+
+class DrugTextEdit(CompletionTextEdit):
+
+ def __init__(self, parent=None):
+ self.known_drugs = []
+ CompletionTextEdit.__init__(self, parent)
+
+ def insertCompletion(self, completion):
+ CompletionTextEdit.insertCompletion(self, completion)
+ self.textCursor().insertText("\n")
+
+ def showEvent(self, event):
+ if self.completer is None:
+ LOGGER.debug("Setting drug list")
+ self.known_drugs = list(medhist.get_medications())
+ self.set_wordset(self.known_drugs)
+
+ def add_new_drug(self, drug):
+ self.known_drugs.append(drug)
+ self.set_wordset(self.known_drugs)
+
+ def sizeHint(self):
+ return QtCore.QSize(400, 100)
+
+ @property
+ def meds(self):
+ for drug in unicode(self.document().toPlainText()).split("\n"):
+ if drug and drug.title() in self.known_drugs:
+ yield drug.title()
+
+ @property
+ def unknown_meds(self):
+ for drug in unicode(self.document().toPlainText()).split("\n"):
+ if drug and drug.title() not in self.known_drugs:
+ yield drug.title()
+
+ def remove_med(self, med):
+ meds = []
+ for drug in unicode(self.document().toPlainText()).split("\n"):
+ if drug and drug.lower() != med.lower():
+ meds.append(drug)
+ self.setText("\n".join(meds))
+
+ def setText(self, text):
+ LOGGER.debug("setting text %s", text)
+ CompletionTextEdit.setText(self, text.strip("\n").title())
+ cursor = self.textCursor()
+ cursor.movePosition(cursor.End, cursor.MoveAnchor)
+ self.setTextCursor(cursor)
+
+
+class MedicalHistoryDialog(BaseDialog):
+
+ def __init__(self, pt, parent=None):
+ BaseDialog.__init__(self, parent, remove_stretch=True)
+ self.pt = pt
+ self.meds_text_edit = DrugTextEdit()
+ patient_label = QtGui.QLabel(
+ "%s<br /><b>%s</b>" % (_("Medical History for"),
+ pt.name_id)
+ )
+ patient_label.setAlignment(QtCore.Qt.AlignCenter)
+
+ self.meds_line_edit = QtGui.QLineEdit()
+ self.meds_line_edit.setMaxLength(200)
+ self.warning_line_edit = QtGui.QLineEdit()
+ self.warning_line_edit.setMaxLength(60)
+ self.allergies_line_edit = QtGui.QLineEdit()
+ self.allergies_line_edit.setMaxLength(60)
+ self.respiratory_line_edit = QtGui.QLineEdit()
+ self.respiratory_line_edit.setMaxLength(60)
+ self.heart_line_edit = QtGui.QLineEdit()
+ self.heart_line_edit.setMaxLength(60)
+ self.bleeding_line_edit = QtGui.QLineEdit()
+ self.bleeding_line_edit.setMaxLength(60)
+ self.arthritis_line_edit = QtGui.QLineEdit()
+ self.arthritis_line_edit.setMaxLength(60)
+ self.diabetes_line_edit = QtGui.QLineEdit()
+ self.diabetes_line_edit.setMaxLength(60)
+ self.infection_line_edit = QtGui.QLineEdit()
+ self.infection_line_edit.setMaxLength(60)
+ self.endocarditis_line_edit = QtGui.QLineEdit()
+ self.endocarditis_line_edit.setMaxLength(60)
+ self.liver_line_edit = QtGui.QLineEdit()
+ self.liver_line_edit.setMaxLength(60)
+ self.anaesthetic_line_edit = QtGui.QLineEdit()
+ self.anaesthetic_line_edit.setMaxLength(60)
+ self.joint_line_edit = QtGui.QLineEdit()
+ self.joint_line_edit.setMaxLength(60)
+ self.heart_surgery_line_edit = QtGui.QLineEdit()
+ self.heart_surgery_line_edit.setMaxLength(60)
+ self.brain_surgery_line_edit = QtGui.QLineEdit()
+ self.brain_surgery_line_edit.setMaxLength(60)
+ self.hospitalised_line_edit = QtGui.QLineEdit()
+ self.hospitalised_line_edit.setMaxLength(60)
+ self.cjd_line_edit = QtGui.QLineEdit()
+ self.cjd_line_edit.setMaxLength(60)
+ self.other_line_edit = QtGui.QLineEdit()
+ self.other_line_edit.setMaxLength(60)
+ self.med_alert_cb = QtGui.QCheckBox(_("Medical Alert"))
+
+ meds_frame = QtGui.QFrame()
+ meds_frame.setMaximumHeight(120)
+ layout = QtGui.QFormLayout(meds_frame)
+ layout.addRow(_("Medications"), self.meds_text_edit)
+ layout.addRow(_("Medication Comments"), self.meds_line_edit)
+
+ l_frame = QtGui.QFrame()
+ layout = QtGui.QFormLayout(l_frame)
+ layout.addRow(_("Warning Card"), self.warning_line_edit)
+ layout.addRow(_("Allergies"), self.allergies_line_edit)
+ layout.addRow(_("Respiratory"), self.respiratory_line_edit)
+ layout.addRow(_("Heart"), self.heart_line_edit)
+ layout.addRow(_("Diabetes"), self.diabetes_line_edit)
+ layout.addRow(_("Arthritis"), self.arthritis_line_edit)
+ layout.addRow(_("Bleeding Disorders"), self.bleeding_line_edit)
+ layout.addRow(_("Infectious Diseases"), self.infection_line_edit)
+ layout.addRow(_("Endocarditis"), self.endocarditis_line_edit)
+
+ r_frame = QtGui.QFrame()
+ layout = QtGui.QFormLayout(r_frame)
+ layout.addRow(_("Liver"), self.liver_line_edit)
+ layout.addRow(_("Anaesthetic"), self.anaesthetic_line_edit)
+ layout.addRow(_("Joint Replacement"), self.joint_line_edit)
+ layout.addRow(_("Heart Surgery"), self.heart_surgery_line_edit)
+ layout.addRow(_("Brain Surgery"), self.brain_surgery_line_edit)
+ layout.addRow(_("Hospitalised"), self.hospitalised_line_edit)
+ layout.addRow(_("CJD"), self.cjd_line_edit)
+ layout.addRow(_("Other"), self.other_line_edit)
+ layout.addRow(_("Mark as Medical Alert"), self.med_alert_cb)
+
+ frame = QtGui.QFrame()
+ vlayout = QtGui.QHBoxLayout(frame)
+ vlayout.setMargin(0)
+ vlayout.addWidget(l_frame)
+ vlayout.addWidget(r_frame)
+
+ scroll_area = QtGui.QScrollArea()
+ scroll_area.setWidgetResizable(True)
+ scroll_area.setWidget(frame)
+
+ self.insertWidget(patient_label)
+ self.insertWidget(meds_frame)
+ self.insertWidget(scroll_area)
+
+ self.mh = None
+ self.new_mh = None
+ self.checked_only = False
+
+ QtCore.QTimer.singleShot(10, self.load_mh)
+ self.enableApply()
+
+ def load_mh(self):
+ self.mh = medhist.get_mh(self.pt.serialno)
+ if self.is_new_mh:
+ return
+
+ def set_text(le, value):
+ if value is None:
+ le.setText("")
+ else:
+ le.setText(value)
+
+ set_text(self.warning_line_edit, self.mh.warning_card)
+ set_text(self.meds_line_edit, self.mh.medication_comments)
+ set_text(self.allergies_line_edit, self.mh.allergies)
+ set_text(self.heart_line_edit, self.mh.heart)
+ set_text(self.diabetes_line_edit, self.mh.diabetes)
+ set_text(self.arthritis_line_edit, self.mh.arthritis)
+ set_text(self.respiratory_line_edit, self.mh.respiratory)
+ set_text(self.bleeding_line_edit, self.mh.bleeding)
+ set_text(self.infection_line_edit, self.mh.infectious_disease)
+ set_text(self.endocarditis_line_edit, self.mh.endocarditis)
+ set_text(self.liver_line_edit, self.mh.liver)
+ set_text(self.anaesthetic_line_edit, self.mh.anaesthetic)
+ set_text(self.joint_line_edit, self.mh.joint_replacement)
+ set_text(self.heart_surgery_line_edit, self.mh.heart_surgery)
+ set_text(self.brain_surgery_line_edit, self.mh.brain_surgery)
+ set_text(self.hospitalised_line_edit, self.mh.hospital)
+ set_text(self.cjd_line_edit, self.mh.cjd)
+ set_text(self.other_line_edit, self.mh.other)
+
+ self.med_alert_cb.setChecked(self.mh.alert)
+
+ self.meds_text_edit.setText(
+ "\n".join(sorted(self.mh.medications.keys())) + "\n")
+
+
+ @property
+ def is_new_mh(self):
+ return self.mh.ix is None
+
+ def advise(self, message):
+ QtGui.QMessageBox.information(self, _("message"), message)
+
+ def sizeHint(self):
+ return QtCore.QSize(1100, 700)
+
+ def showEvent(self, event):
+ self.meds_text_edit.setFocus()
+
+ @property
+ def meds(self):
+ return self.meds_text_edit.meds
+
+ @property
+ def unknown_meds(self):
+ return self.meds_text_edit.unknown_meds
+
+ def check_new_meds(self):
+ for med in self.unknown_meds:
+ LOGGER.debug("unknown medication found %s", med)
+ result = QtGui.QMessageBox.question(
+ self,
+ _("question"),
+ "<b>'%s'</b> %s<hr />%s" % (
+ med,
+ _("is not a known drug on the system"),
+ _("Would you like to add it?")
+ ),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No)
+ if result == QtGui.QMessageBox.Yes:
+ medhist.insert_medication(med)
+ self.meds_text_edit.add_new_drug(med)
+ else:
+ if QtGui.QMessageBox.question(
+ self,
+ _("question"),
+ "%s <b>'%s'</b> %s" % (
+ _("Delete"),
+ med,
+ _("from your input?")
+ ),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
+ return False
+ else:
+ self.meds_text_edit.remove_med(med)
+ return True
+
+ def get_new_mh(self, rejecting=False):
+ '''
+ checks what has been entered, and saves it to self.new_mh
+ returns (result, checked_only)
+ result = whether any changes have been applied.
+ checked_only = bool
+ '''
+ result = True
+ if not rejecting:
+ result = self.check_new_meds()
+ meds_dict = {}
+ for med in self.meds:
+ meds_dict[med] = ""
+ if med not in self.mh.medications:
+ LOGGER.debug("new medication %s", med)
+ for med in self.unknown_meds:
+ if med not in self.mh.medications:
+ LOGGER.debug("unknown new medication found %s", med)
+ meds_dict[med] = ""
+ for med in self.mh.medications:
+ if med not in self.meds:
+ LOGGER.debug("deleted medication %s", med)
+ self.new_mh = medhist.MedHist(
+ None, # ix
+ unicode(self.warning_line_edit.text().toUtf8()),
+ meds_dict,
+ unicode(self.meds_line_edit.text().toUtf8()),
+ unicode(self.allergies_line_edit.text().toUtf8()),
+ unicode(self.respiratory_line_edit.text().toUtf8()),
+ unicode(self.heart_line_edit.text().toUtf8()),
+ unicode(self.diabetes_line_edit.text().toUtf8()),
+ unicode(self.arthritis_line_edit.text().toUtf8()),
+ unicode(self.bleeding_line_edit.text().toUtf8()),
+ unicode(self.infection_line_edit.text().toUtf8()),
+ unicode(self.endocarditis_line_edit.text().toUtf8()),
+ unicode(self.liver_line_edit.text().toUtf8()),
+ unicode(self.anaesthetic_line_edit.text().toUtf8()),
+ unicode(self.joint_line_edit.text().toUtf8()),
+ unicode(self.heart_surgery_line_edit.text().toUtf8()),
+ unicode(self.brain_surgery_line_edit.text().toUtf8()),
+ unicode(self.hospitalised_line_edit.text().toUtf8()),
+ unicode(self.cjd_line_edit.text().toUtf8()),
+ unicode(self.other_line_edit.text().toUtf8()),
+ self.med_alert_cb.isChecked(),
+ localsettings.currentDay(),
+ None
+ )
+ return result
+
+ @property
+ def has_edits(self):
+ if self.new_mh is None:
+ return False
+ has_edits = False
+ for prop in (medhist.PROPERTIES):
+ if prop in ("ix", "time_stamp", "chkdate"):
+ continue
+ old_val = self.mh.__dict__[prop]
+ new_val = self.new_mh.__dict__[prop]
+ if old_val is None:
+ old_val = ""
+ if old_val != new_val:
+ LOGGER.debug(
+ "changed item %s '%s' -> '%s'", prop, old_val, new_val)
+ has_edits = True
+ return has_edits
+
+ def accept(self):
+ if not self.get_new_mh():
+ return
+ if not self.has_edits and self.mh.chkdate != self.new_mh.chkdate:
+ if QtGui.QMessageBox.question(
+ self,
+ _("question"),
+ _("No changes - mark as checked today?"),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
+ self.checked_only = True
+ else:
+ BaseDialog.reject(self)
+ return
+ elif self.is_new_mh and not self.has_edits:
+ if QtGui.QMessageBox.question(
+ self,
+ _("question"),
+ _("Blank Medical History Entered - mark as checked today?"),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
+ BaseDialog.reject(self)
+ return
+ BaseDialog.accept(self)
+
+ def apply(self):
+ LOGGER.debug("applying changes")
+ if self.has_edits:
+ self.pt.addHiddenNote(
+ "mednotes", _("Updated Medical History"), one_only=True)
+ elif self.is_new_mh or self.checked_only:
+ self.update_chkdate()
+ self.pt.addHiddenNote(
+ "mednotes", _("Checked Medical History"), one_only=True)
+ self.save_mh()
+ self.pt.mh_chkdate = self.new_mh.chkdate
+ self.pt.MEDALERT = self.new_mh.alert
+
+ def reject(self):
+ self.get_new_mh(rejecting=True)
+ if self.has_edits:
+ if QtGui.QMessageBox.question(
+ self,
+ _("Confirm"),
+ _("Abandon your changes?"),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
+ return
+ BaseDialog.reject(self)
+
+ def save_mh(self):
+ '''
+ save the medical history which has been entered.
+ overwrite any edits made earlier on the same day.
+ '''
+ if self.is_new_mh or self.new_mh.chkdate != self.mh.time_stamp.date():
+ LOGGER.info("writing new mh %s", self.new_mh)
+ medhist.insert_mh(self.pt.serialno, self.new_mh)
+ else:
+ LOGGER.info("updating today's medical history")
+ medhist.update_mh(self.mh.ix, self.new_mh)
+
+ def update_chkdate(self):
+ LOGGER.info("updating chkdate for existing mh")
+ medhist.update_chkdate(self.mh.ix)
+
+if __name__ == "__main__":
+ app = QtGui.QApplication([])
+ from openmolar.dbtools import patient_class
+ from openmolar.settings.localsettings import PatientNotFoundError
+
+ LOGGER.setLevel(logging.DEBUG)
+ i = 16539
+ try:
+ pt = patient_class.patient(i)
+ except PatientNotFoundError:
+ LOGGER.warning("no such serialno %s", i)
+ dl = MedicalHistoryDialog(pt)
+ if dl.exec_():
+ LOGGER.debug("dialogl accepted")
+ dl.apply()
+ else:
+ LOGGER.debug("dialogl rejected")
diff --git a/src/openmolar/qt4gui/diary_widget.py b/src/openmolar/qt4gui/diary_widget.py
index 9fa17a7..92bdd66 100644
--- a/src/openmolar/qt4gui/diary_widget.py
+++ b/src/openmolar/qt4gui/diary_widget.py
@@ -1424,7 +1424,8 @@ class DiaryWidget(QtGui.QWidget):
self.advise(_("Error Removing from Appointment Book"), 2)
self.layout_dayView()
- self.pt_diary_changed.emit(self.pt.serialno)
+ if self.pt is not None:
+ self.pt_diary_changed.emit(self.pt.serialno)
def clearEmergencySlot(self, arg):
'''
diff --git a/src/openmolar/qt4gui/fees/daybook_module.py b/src/openmolar/qt4gui/fees/daybook_module.py
index e2b5ec1..ddeab25 100644
--- a/src/openmolar/qt4gui/fees/daybook_module.py
+++ b/src/openmolar/qt4gui/fees/daybook_module.py
@@ -124,9 +124,6 @@ def updateDaybook(om_gui):
daybook.add(om_gui.pt.serialno, om_gui.pt.cset, dent, trtid,
daybookdict, feesa, feesb, hashes)
- LOGGER.debug("daybook_module - updating pd4")
- om_gui.pt.pd4 = localsettings.currentDay()
-
def daybookView(om_gui, print_=False):
dent1 = str(om_gui.ui.daybookDent1ComboBox.currentText())
diff --git a/src/openmolar/qt4gui/fees/fees_module.py b/src/openmolar/qt4gui/fees/fees_module.py
index 840ba95..0d2b7bb 100644
--- a/src/openmolar/qt4gui/fees/fees_module.py
+++ b/src/openmolar/qt4gui/fees/fees_module.py
@@ -130,20 +130,18 @@ def takePayment(om_gui):
LOGGER.debug(
"Payment patient is not loaded. skipping receipt offer.")
- patient_write_changes.toNotes(paymentPt.serialno,
- paymentPt.HIDDENNOTES)
-
LOGGER.debug("writing payment notes")
- if (patient_write_changes.discreet_money_changes(
- paymentPt, ("money2", "money3", "money11")) and
- om_gui.pt.serialno != 0
- ):
+ om_gui.pt.reset_billing()
+ if (patient_write_changes.discreet_changes(
+ paymentPt,
+ ("money2", "money3", "money11", "billdate", "billct", "billtype"))
+ and om_gui.pt.serialno != 0):
LOGGER.debug("updating patient's stored money values")
om_gui.pt.dbstate.money2 = om_gui.pt.money2
om_gui.pt.dbstate.money3 = om_gui.pt.money3
om_gui.pt.dbstate.money11 = om_gui.pt.money11
+ om_gui.pt.dbstate.reset_billing()
- paymentPt.clearHiddenNotes()
om_gui.updateDetails()
om_gui.updateHiddenNotesLabel()
LOGGER.info("PAYMENT ALL DONE!")
@@ -354,6 +352,7 @@ def makeBadDebt(om_gui):
if result == QtGui.QMessageBox.Yes:
#--what is owed
om_gui.pt.money11 = om_gui.pt.fees
+ om_gui.pt.force_money_changes = True
om_gui.pt.resetAllMonies()
om_gui.pt.status = "BAD DEBT"
om_gui.ui.notesEnter_textEdit.setText(
@@ -365,17 +364,21 @@ def makeBadDebt(om_gui):
def populateAccountsTable(om_gui):
+ om_gui.advise(_("Loading Accounts Table"))
+ om_gui.wait()
rows = accounts.details()
om_gui.ui.accounts_tableWidget.clear()
om_gui.ui.accounts_tableWidget.setSortingEnabled(False)
om_gui.ui.accounts_tableWidget.setRowCount(len(rows))
headers = ("Dent", "Serialno", "", "First", "Last", "DOB", "Memo",
- "Last Appt", "Last Bill", "Type", "Number", "T/C", "Fees", "A", "B",
+ "Last Tx", "Last Bill", "Type", "Number", "T/C", "Fees", "A", "B",
"C")
om_gui.ui.accounts_tableWidget.setColumnCount(len(headers))
om_gui.ui.accounts_tableWidget.setHorizontalHeaderLabels(headers)
om_gui.ui.accounts_tableWidget.verticalHeader().hide()
+ om_gui.ui.accounts_tableWidget.horizontalHeader().setStretchLastSection(
+ True)
rowno = 0
total = 0
for row in rows:
@@ -401,10 +404,11 @@ def populateAccountsTable(om_gui):
# item.setText(localsettings.formatMoney(d))
elif col == 11:
- if d > 0:
- item.setText("N")
+ if d is None:
+ item.setText(_("Under Treatment"))
else:
- item.setText("Y")
+ item.setData(QtCore.Qt.DisplayRole,
+ QtCore.QVariant(QtCore.QDate(d)))
else:
item.setText(str(d).title())
om_gui.ui.accounts_tableWidget.setItem(rowno, col, item)
@@ -419,3 +423,4 @@ def populateAccountsTable(om_gui):
for i in range(om_gui.ui.accounts_tableWidget.columnCount()):
om_gui.ui.accounts_tableWidget.resizeColumnToContents(i)
om_gui.ui.accountsTotal_doubleSpinBox.setValue(total / 100)
+ om_gui.wait(False)
diff --git a/src/openmolar/qt4gui/fees/manipulate_plan.py b/src/openmolar/qt4gui/fees/manipulate_plan.py
index 8742288..782db4b 100644
--- a/src/openmolar/qt4gui/fees/manipulate_plan.py
+++ b/src/openmolar/qt4gui/fees/manipulate_plan.py
@@ -273,6 +273,7 @@ def xrayAdd(om_gui, complete=False):
# offerTreatmentItems is a generator, so the list conversion here
# is so that the dialog get raised before the
#"were these xrays taken today question
+
chosen_treatments = list(offerTreatmentItems(om_gui, mylist, complete))
if not chosen_treatments:
@@ -286,10 +287,33 @@ def xrayAdd(om_gui, complete=False):
if input == QtGui.QMessageBox.Yes:
complete = True
- add_treatments_to_plan(om_gui, chosen_treatments, complete)
+ completed_planned_warning_required = False
+ # complete any xrays already planned.
+ if complete:
+ pt = om_gui.pt
+ courseno = pt.treatment_course.courseno
+ for xray, trt in chosen_treatments:
+ if trt in pt.treatment_course.xraypl:
+ n_txs = pt.treatment_course.xraycmp.split(" ").count(trt) + 1
+ hash_ = localsettings.hash_func(
+ "%sxray%s%s" %
+ (courseno, n_txs, trt))
+ tx_hash = TXHash(hash_)
+ tx_hash_complete(om_gui, tx_hash)
+ completed_planned_warning_required = True
+ else:
+ add_treatments_to_plan(om_gui, ((xray, trt),), True)
+ else:
+ add_treatments_to_plan(om_gui, chosen_treatments, False)
+
if om_gui.ui.tabWidget.currentIndex() == 4: # clinical summary
om_gui.load_clinicalSummaryPage()
+ else:
+ om_gui.ui.completed_listView.model().reset()
+ if completed_planned_warning_required:
+ om_gui.advise(
+ _("Some of the xrays you completed were already planned."), 1)
def denture_add(om_gui):
dl = DentureDialog(om_gui)
diff --git a/src/openmolar/qt4gui/forum_gui_module.py b/src/openmolar/qt4gui/forum_gui_module.py
index 7c9ceb9..723923a 100644
--- a/src/openmolar/qt4gui/forum_gui_module.py
+++ b/src/openmolar/qt4gui/forum_gui_module.py
@@ -242,7 +242,7 @@ def forumReply(om_gui):
post = forum.post()
post.parent_ix = parentix
post.topic = dl.topic_lineEdit.text().toAscii()
- post.comment = dl.comment_textEdit.toPlainText().toAscii()[:255]
+ post.comment = dl.comment_textEdit.toPlainText().toAscii()
post.inits = dl.from_comboBox.currentText()
post.recipient = dl.to_comboBox.currentText()
forum.commitPost(post)
diff --git a/src/openmolar/qt4gui/maingui.py b/src/openmolar/qt4gui/maingui.py
index 264073a..c6bd0ba 100755
--- a/src/openmolar/qt4gui/maingui.py
+++ b/src/openmolar/qt4gui/maingui.py
@@ -137,6 +137,9 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
fee_table_tester = None
phrasebook_editor = None
entering_new_patient = False
+ reception_notes_loaded = False
+ summary_notes_loaded = False
+ notes_loaded = False
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
@@ -540,7 +543,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
def flipDeciduous(self):
'''
- toggle the selected tooth's deciduos state
+ toggle the selected tooth's deciduous state
'''
charts_gui.flipDeciduous(self)
@@ -570,7 +573,16 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
user has clicked on the delete all option from a tooth's right click
menu
'''
- self.advise("add comments for tooth %s not working yet" % tooth)
+ cb = self.ui.toothPropsWidget.comments_comboBox
+ comment, result = QtGui.QInputDialog.getItem(
+ self,
+ _("Add comment"),
+ "%s %s" % (_("Add a comment to tooth"), tooth.upper()),
+ [cb.itemText(i) for i in range(1, cb.count())],
+ current=-1,
+ editable=True)
+ if result:
+ self.ui.toothPropsWidget.comments(comment)
def chooseTooth(self):
'''
@@ -724,6 +736,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
#--go to either "reception" or "clinical summary"
self.gotoDefaultTab()
+ self.load_notes()
def clearRecord(self):
'''
@@ -732,14 +745,8 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
Other pages are disabled.
'''
if self.pt.serialno != 0:
- LOGGER.debug("updating last_address_details")
- localsettings.LAST_ADDRESS = (
- self.pt.sname, self.pt.addr1, self.pt.addr2,
- self.pt.addr3, self.pt.town, self.pt.county,
- self.pt.pcde, self.pt.tel1)
- LOGGER.debug("details are %s", str(localsettings.LAST_ADDRESS))
-
# print "clearing record"
+ self.forget_notes_loaded()
self.ui.dobEdit.setDate(QtCore.QDate(1900, 1, 1))
self.ui.detailsBrowser.setText("")
self.ui.notes_webView.setHtml("")
@@ -756,8 +763,8 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.ui.summaryChartWidget):
chart.clear()
chart.update()
- self.ui.notesSummary_webView.setHtml(localsettings.message)
- self.ui.reception_textBrowser.setHtml(localsettings.message)
+ self.ui.notesSummary_webView.setHtml("")
+ self.ui.reception_textBrowser.setHtml("")
self.ui.recNotes_webView.setHtml("")
self.ui.chartsTableWidget.clear()
# self.diary_widget.schedule_controller.clear()
@@ -774,6 +781,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.load_editpage()
self.editPageVisited = False
else:
+ self.load_notes()
self.pt.familyno = None
self.update_family_label()
@@ -786,23 +794,6 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
else:
self.ui.tabWidget.setCurrentIndex(3)
- def load_clinicalSummaryPage(self):
- self.ui.planSummary_textBrowser.setHtml(plan.summary(self.pt))
-
- def load_receptionSummaryPage(self):
- '''
- load the reception views
- '''
- if self.pt.serialno == 0:
- self.ui.reception_textBrowser.setHtml(localsettings.message)
- else:
- html_ = reception_summary.html(self.pt)
- self.ui.reception_textBrowser.setText(html_)
- self.pt_diary_widget.layout_ptDiary()
- note = formatted_notes.rec_notes(self.pt.notes_dict,
- self.pt.treatment_course.accd)
- self.ui.recNotes_webView.setHtml(note)
-
def webviewloaded(self):
'''
a notes web view has loaded..
@@ -896,6 +887,9 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
'''
called by the user clicking the new patient button
'''
+ if self.pt:
+ localsettings.LAST_ADDRESS = self.pt.address_tuple
+ localsettings.last_family_no = self.pt.familyno
new_patient_gui.enterNewPatient(self)
def checkNewPatient(self):
@@ -1192,9 +1186,19 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.advise(_("Not loading patient"))
return
+ if self.pt:
+ current_address = self.pt.address_tuple
+ else:
+ current_address = localsettings.LAST_ADDRESS
+
try:
+ # update saved last address
self.pt = patient_class.patient(serialno)
self.pt_diary_widget.set_patient(self.pt)
+ if (current_address == localsettings.BLANK_ADDRESS or
+ self.pt.address_tuple != current_address):
+ localsettings.LAST_ADDRESS = current_address
+ localsettings.last_family_no = self.pt.familyno
try:
self.loadpatient(newPatientReload=newPatientReload)
@@ -1252,19 +1256,60 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.ui.summary_notes_checkBox.isChecked()
def updateNotesPage(self):
+ i = self.ui.tabWidget.currentIndex()
+ LOGGER.debug("update notes page called, ignore=%s", i!=5)
+ if i !=5 or self.notes_loaded:
+ return
self.set_note_preferences()
note_html = formatted_notes.notes(self.pt.notes_dict)
self.ui.notes_webView.setHtml(note_html)
page = self.ui.notes_webView.page()
page.setLinkDelegationPolicy(page.DelegateAllLinks)
+ self.notes_loaded = True
+
+ def load_receptionSummaryPage(self):
+ '''
+ load the reception views
+ '''
+ i = self.ui.tabWidget.currentIndex()
+ LOGGER.debug(
+ "update reception Summary page called, ignore=%s", i!=3)
+ if self.pt.serialno == 0:
+ self.ui.reception_textBrowser.setHtml(localsettings.message)
+ elif i == 3:
+ html_ = reception_summary.html(self.pt)
+ self.ui.reception_textBrowser.setText(html_)
+ self.pt_diary_widget.layout_ptDiary()
+ if not self.reception_notes_loaded:
+ note = formatted_notes.rec_notes(
+ self.pt.notes_dict,
+ self.pt.treatment_course.accd
+ )
+ self.ui.recNotes_webView.setHtml(note)
+ self.reception_notes_loaded = True
+
+ def load_clinicalSummaryPage(self):
+ i = self.ui.tabWidget.currentIndex()
+ LOGGER.debug("load clinical summary page called, ignore=%s", i!=4)
+ if i == 4:
+ self.ui.planSummary_textBrowser.setHtml(plan.summary(self.pt))
+ self.load_notes_summary()
def load_notes_summary(self):
- self.set_note_preferences()
- note_html = formatted_notes.summary_notes(self.pt.notes_dict)
- self.ui.notesSummary_webView.setHtml(note_html)
- page = self.ui.notesSummary_webView.page()
- page.setLinkDelegationPolicy(page.DelegateAllLinks)
+ i = self.ui.tabWidget.currentIndex()
+ LOGGER.debug("load clinical summary notes called, ignore=%s", i!=4)
+ if i != 4:
+ LOGGER.debug("ignoring clinical summary notes load - tab hidden")
+ elif self.pt.serialno == 0:
+ self.ui.notesSummary_webView.setHtml(localsettings.message)
+ elif not self.summary_notes_loaded:
+ self.set_note_preferences()
+ note_html = formatted_notes.summary_notes(self.pt.notes_dict)
+ self.ui.notesSummary_webView.setHtml(note_html)
+ page = self.ui.notesSummary_webView.page()
+ page.setLinkDelegationPolicy(page.DelegateAllLinks)
+ self.summary_notes_loaded = True
def loadpatient(self, newPatientReload=False):
'''
@@ -1279,7 +1324,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.ui.tabWidget.setCurrentIndex(4)
else:
self.ui.tabWidget.setCurrentIndex(3)
- self.load_receptionSummaryPage()
+ self.forget_notes_loaded()
self.ui.actionFix_Locked_New_Course_of_Treatment.setEnabled(False)
#--populate dnt1 and dnt2 comboboxes
if not self.pt.dnt1:
@@ -1293,7 +1338,8 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.pt.checkExemption()
self.updateDetails()
self.ui.synopsis_lineEdit.setText(self.pt.synopsis)
- self.load_notes_summary()
+ self.load_clinicalSummaryPage()
+ self.load_receptionSummaryPage()
self.ui.notes_webView.setHtml("")
self.ui.notesEnter_textEdit.setText("")
@@ -1381,13 +1427,12 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
else:
self.ui.medNotes_pushButton.setStyleSheet("")
- if self.pt.MH is not None:
- mhdate = self.pt.MH[13]
- if mhdate is None:
- chkdate = ""
- else:
- chkdate = " - %s" % localsettings.formatDate(mhdate)
- self.ui.medNotes_pushButton.setText("MedNotes%s" % chkdate)
+ mhdate = self.pt.mh_chkdate
+ if mhdate is None:
+ chkdate = ""
+ else:
+ chkdate = " - %s" % localsettings.formatDate(mhdate)
+ self.ui.medNotes_pushButton.setText("MedNotes%s" % chkdate)
self.enableEdit(True)
@@ -1710,11 +1755,10 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
if self.pt.serialno == 0:
self.advise("no patient selected", 1)
return
-
- dl = MedNotesDialog(self.pt, self)
+ dl = MedicalHistoryDialog(self.pt, self)
if dl.exec_():
dl.apply()
- self.advise("Updated Medical Notes")
+ self.advise(_("Updated/Checked Medical Notes"))
self.medalert()
self.updateHiddenNotesLabel()
@@ -1784,6 +1828,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
if self.editPageVisited:
#-- only make changes if user has visited this tab
self.apply_editpage_changes()
+ self.pt.monies_reset = patient_write_changes.reset_money(self.pt)
uc = self.unsavedChanges()
if uc != []:
LOGGER.info(
@@ -1824,21 +1869,25 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
#--result will be a "line number" or -1 if unsuccessful write
self.ui.notesEnter_textEdit.setText("")
self.ui.hiddenNotes_label.setText("")
- self.pt.getNotesTuple()
#--reload the notes
- html = formatted_notes.notes(self.pt.notes_dict)
- self.ui.notesSummary_webView.setHtml(html)
-
- if self.ui.tabWidget.currentIndex() == 3:
- self.load_receptionSummaryPage()
-
- if self.ui.tabWidget.currentIndex() == 5:
- self.updateNotesPage()
+ self.pt.getNotesTuple()
+ self.load_notes()
else:
#--exception writing to db
self.advise("error writing notes to database... sorry!", 2)
self.updateDetails()
+ def forget_notes_loaded(self):
+ self.reception_notes_loaded = False
+ self.summary_notes_loaded = False
+ self.notes_loaded = False
+
+ def load_notes(self):
+ self.forget_notes_loaded()
+ self.load_receptionSummaryPage()
+ self.load_notes_summary()
+ self.updateNotesPage()
+
def enableEdit(self, arg=True):
'''
disable/enable widgets "en mass" when no patient loaded
@@ -2176,7 +2225,8 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
'''
user has decided to reclassify a patient as a "bad debt" patient
'''
- fees_module.makeBadDebt(self)
+ if permissions.granted():
+ fees_module.makeBadDebt(self)
def loadAccountsTable_clicked(self):
'''
@@ -2375,7 +2425,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
show how the current estimate has changed
'''
self.debug_browser_refresh_func = partial(
- est_logger.html_history, self.pt.serialno)
+ est_logger.html_history, self.pt.courseno0)
self.refresh_debug_browser()
def nhsClaimsShortcut(self):
@@ -2570,8 +2620,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
if dl.exec_():
if patient_loaded:
self.pt.getNotesTuple()
- self.updateNotesPage()
- self.load_notes_summary()
+ self.load_notes()
def show_diary(self):
'''
@@ -2655,7 +2704,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
'''
a function to connect all the receptionists buttons
'''
- self.ui.printAccount_pushButton.clicked.connect(self.printaccount)
+ self.ui.printAccount_pushButton.pressed.connect(self.printaccount)
self.ui.printEst_pushButton.clicked.connect(self.printEstimate)
self.ui.printRecall_pushButton.clicked.connect(self.printrecall)
self.ui.takePayment_pushButton.clicked.connect(
@@ -2750,6 +2799,9 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.ui.actionAdd_Clinician.triggered.connect(self.add_clinician)
self.ui.actionEdit_Practice_Details.triggered.connect(
self.edit_practice)
+ self.ui.actionEdit_Standard_Letters.triggered.connect(
+ self.edit_standard_letters)
+ self.ui.actionEdit_Feescales.triggered.connect(self.feetable_xml)
def signals_estimates(self):
# Estimates and Course Management
@@ -2964,8 +3016,7 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
self.ui.notes_includeTimestamps_checkBox,
self.ui.notes_includeMetadata_checkBox,
self.ui.summary_notes_checkBox):
- rb.toggled.connect(self.updateNotesPage)
- rb.toggled.connect(self.load_notes_summary)
+ rb.toggled.connect(self.load_notes)
def signals_tabs(self, connect=True):
'''
@@ -3115,7 +3166,6 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
_("Member(s)")
)
message_2 += " (%d)" % (self.pt.n_family_members - 1)
- localsettings.last_family_no = self.pt.familyno
elif self.pt.serialno == 0:
message = _("No Patient Loaded")
else:
@@ -3392,6 +3442,11 @@ class OpenmolarGui(QtGui.QMainWindow, Advisor):
if dl.exec_():
self.advise(_("Practice Name and/or Address modified."), 1)
+ def edit_standard_letters(self):
+ dl = EditStandardLettersDialog(self)
+ if dl.exec_():
+ CorrespondenceDialog.LETTERS = None
+
def clear_todays_emergencies(self):
self.show_diary()
self.diary_widget.clearTodaysEmergencyTime()
diff --git a/src/openmolar/qt4gui/new_patient_gui.py b/src/openmolar/qt4gui/new_patient_gui.py
index 6cda6be..b120d25 100644
--- a/src/openmolar/qt4gui/new_patient_gui.py
+++ b/src/openmolar/qt4gui/new_patient_gui.py
@@ -34,15 +34,16 @@ LOGGER = logging.getLogger("openmolar")
def check_use_family(om_gui):
- if localsettings.LAST_ADDRESS == ("",) * 8:
+ if localsettings.LAST_ADDRESS == localsettings.BLANK_ADDRESS:
+ LOGGER.warning("New Patient - No previous record details found")
return
- result = QtGui.QMessageBox.question(om_gui,
- _("Question"),
- _(
- "Use details from the previous record?"),
- QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
- QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes
- if result:
+ if QtGui.QMessageBox.question(
+ om_gui,
+ _("Question"),
+ _(
+ "Use details from the previous record?"),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
dup_tup = localsettings.LAST_ADDRESS
om_gui.ui.addr1Edit.setText(dup_tup[1])
om_gui.ui.addr2Edit.setText(dup_tup[2])
diff --git a/src/openmolar/qt4gui/printing/bulk_mail.py b/src/openmolar/qt4gui/printing/bulk_mail.py
index 13a593a..bbb7672 100644
--- a/src/openmolar/qt4gui/printing/bulk_mail.py
+++ b/src/openmolar/qt4gui/printing/bulk_mail.py
@@ -54,9 +54,11 @@ FAMILY_BODY = '''%s\n%s''' % (
SIGN_OFF = _("Yours sincerely,")
-FOOTER = _('''* If you already have a future appointment with us -
-please accept our apologies and ignore this letter.''')
+PS_TEXT = _('* P.S If you already have a future appointment with us - '
+ 'please accept our apologies and ignore this letter.')
+FOOTER = _('We are currently accepting new patients to the practice.'
+ 'We would be delighted if you would recommend us to your friends and family.')
try:
filepath = os.path.join(localsettings.localFileDirectory,
@@ -68,16 +70,6 @@ except IOError:
LOGGER.warning("no recall footer found in '%s'" % filepath)
CUSTOM_TEXT = ""
-try:
- filepath = os.path.join(localsettings.localFileDirectory,
- "recall_postscript.txt")
- f = open(filepath, "r")
- PS_TEXT = f.read()
- f.close()
-except IOError:
- LOGGER.warning("no recall ps found in %s" % filepath)
- PS_TEXT = ""
-
class OMLetter(object):
@@ -438,14 +430,14 @@ class bulkMails(object):
if not dialog.exec_():
return
- font = QtGui.QFont("Sans", 11)
+ font = QtGui.QFont("Helvetica", 11)
fm = QtGui.QFontMetrics(font)
line_height = fm.height()
italic_font = QtGui.QFont(font)
italic_font.setItalic(True)
- sigFont = QtGui.QFont("URW Chancery L", 15)
+ sigFont = QtGui.QFont("URW Chancery L", 18)
sigFont.setBold(True)
sig_font_height = QtGui.QFontMetrics(sigFont).height() * 1.2
@@ -458,8 +450,8 @@ class bulkMails(object):
ADDRESS_LEFT = 80
ADDRESS_HEIGHT = 140
- FOOTER_HEIGHT = 150
- DATE_HEIGHT = 1 * line_height
+ FOOTER_HEIGHT = 180
+ DATE_HEIGHT = 2 * line_height
BODY_HEIGHT = pageRect.height() - (
TOP + ADDRESS_HEIGHT + FOOTER_HEIGHT + DATE_HEIGHT)
@@ -591,7 +583,7 @@ class bulkMails(object):
line_count = PS_TEXT.count("\n") + 2
ps_rect = QtCore.QRectF(
body_rect.bottomLeft().x(),
- sig_rect.bottomLeft().y() + line_height,
+ sig_rect.bottomLeft().y() + line_height*2,
bodyRect.width(), line_height * line_count)
painter.setFont(font)
@@ -601,7 +593,7 @@ class bulkMails(object):
painter.drawRect(ps_rect.adjusted(2, 2, -2, -2))
# footer
- option = QtGui.QTextOption(QtCore.Qt.AlignCenter)
+ option = QtGui.QTextOption(QtCore.Qt.AlignHCenter)
option.setWrapMode(QtGui.QTextOption.WordWrap)
painter.drawLine(footerRect.topLeft(), footerRect.topRight())
@@ -633,8 +625,8 @@ if __name__ == "__main__":
om_gui = maingui.OpenmolarGui()
- conditions = "recd>=%s and recd<=%s and dnt1=%s"
- values = date(2012, 7, 1), date(2012, 7, 13), 6
+ conditions = "new_patients.serialno=%s"
+ values = (1,)
patients = recall.getpatients(conditions, values)
letters = bulkMails(om_gui)
diff --git a/src/openmolar/qt4gui/printing/gp17/gp17_data.py b/src/openmolar/qt4gui/printing/gp17/gp17_data.py
index e6b161b..0653740 100644
--- a/src/openmolar/qt4gui/printing/gp17/gp17_data.py
+++ b/src/openmolar/qt4gui/printing/gp17/gp17_data.py
@@ -321,6 +321,7 @@ test_complex_codes = [
class DuckCourse(object):
accd = date(1969, 12, 9)
cmpd = date(2015, 12, 9)
+ ftr = True
class DuckPatient(object):
diff --git a/src/openmolar/qt4gui/printing/letterprint.py b/src/openmolar/qt4gui/printing/letterprint.py
index 6171e57..2111353 100644
--- a/src/openmolar/qt4gui/printing/letterprint.py
+++ b/src/openmolar/qt4gui/printing/letterprint.py
@@ -35,7 +35,7 @@ class letter():
def printpage(self, askfirst=True):
dialog = QtGui.QPrintDialog(self.printer)
if askfirst and not dialog.exec_():
- return
+ return False
document = QtGui.QTextDocument()
document.setHtml(self.html)
document.print_(self.printer)
diff --git a/src/openmolar/qt4gui/printing/om_printing.py b/src/openmolar/qt4gui/printing/om_printing.py
index f7a6ca9..42d9e1e 100644
--- a/src/openmolar/qt4gui/printing/om_printing.py
+++ b/src/openmolar/qt4gui/printing/om_printing.py
@@ -37,17 +37,15 @@ from PyQt4 import QtGui, QtCore
from openmolar.settings import localsettings, utilities
from openmolar.ptModules import estimates
-from openmolar.ptModules import standardletter
from openmolar.dbtools import docsprinted
from openmolar.dbtools import appointments
from openmolar.dbtools import patient_class
from openmolar.dbtools import patient_write_changes
from openmolar.dbtools import referral
+from openmolar.dbtools import standard_letter
-from openmolar.qt4gui.compiled_uis import Ui_enter_letter_text
from openmolar.qt4gui.compiled_uis import Ui_daylist_print
-from openmolar.qt4gui.compiled_uis import Ui_ortho_ref_wizard
#--modules which use qprinter
from openmolar.qt4gui.printing import receiptPrint
@@ -61,6 +59,7 @@ from openmolar.qt4gui.printing import accountPrint
from openmolar.qt4gui.printing import estimatePrint
from openmolar.qt4gui.printing.mh_print import MHPrint
+from openmolar.qt4gui.dialogs.correspondence_dialog import CorrespondenceDialog
from openmolar.qt4gui.dialogs.print_record_dialog import PrintRecordDialog
LOGGER = logging.getLogger("openmolar")
@@ -118,26 +117,25 @@ def printLetter(om_gui):
if om_gui.pt.serialno == 0:
om_gui.advise(_("no patient selected"), 1)
return
- html = standardletter.getHtml(om_gui.pt)
- Dialog = QtGui.QDialog()
- dl = Ui_enter_letter_text.Ui_Dialog()
- dl.setupUi(Dialog)
- dl.textEdit.setHtml(html)
- referred_pt = om_gui.pt
- Dialog.show()
-
- if Dialog.exec_():
- html = dl.textEdit.toHtml()
- myclass = letterprint.letter(html)
- myclass.printpage()
- html = str(html.toAscii())
- docsprinted.add(referred_pt.serialno, "std letter (html)", html)
- referred_pt.addHiddenNote("printed", "std letter")
- if referred_pt == om_gui.pt:
- if om_gui.ui.prevCorres_treeWidget.isVisible():
- om_gui.docsPrintedInit()
- else:
- referred_pt.toNotes(referred_pt.serialno, referred_pt.HIDDENNOTES)
+ html = standard_letter.getHtml(om_gui.pt)
+ dl = CorrespondenceDialog(html, om_gui.pt, parent=None)
+ dl.show()
+
+ if dl.exec_():
+ letter = letterprint.letter(dl.text)
+ if letter.printpage():
+ docsprinted.add(dl.pt.serialno,
+ "%s (html)" % dl.letter_description,
+ dl.text)
+ dl.pt.addHiddenNote("printed",
+ "%s %s" % (_("letter"), dl.letter_description)
+ )
+ if dl.pt == om_gui.pt:
+ if om_gui.ui.prevCorres_treeWidget.isVisible():
+ om_gui.docsPrintedInit()
+ om_gui.updateHiddenNotesLabel()
+ else:
+ dl.pt.toNotes(dl.pt.serialno, dl.pt.HIDDENNOTES)
def printAccountsTable(om_gui):
@@ -211,7 +209,7 @@ def customEstimate(om_gui, html=""):
om_gui.advise(_("no patient selected"), 1)
return
if html == "":
- html = standardletter.getHtml(om_gui.pt)
+ html = standard_letter.getHtml(om_gui.pt)
pt_total = 0
ehtml = "<br />%s" % _(
"Estimate for your current course of treatment.")
@@ -266,78 +264,50 @@ def htmlEditor(om_gui, type="", html="", version=0):
'''
raise a dialog to print an html editor
'''
- Dialog = QtGui.QDialog(om_gui)
- dl = Ui_enter_letter_text.Ui_Dialog()
- dl.setupUi(Dialog)
- dl.textEdit.setHtml(html)
- if Dialog.exec_():
- html = dl.textEdit.toHtml()
- myclass = letterprint.letter(html)
- myclass.printpage()
-
- html = str(dl.textEdit.toHtml().toAscii())
-
- docsprinted.add(
- om_gui.pt.serialno,
- "%s (html)" %
- type,
- html,
- version +
- 1)
+ dl = CorrespondenceDialog(html, om_gui.pt, preformatted=False, parent=None)
+ dl.show()
+
+ if dl.exec_():
+ letter = letterprint.letter(dl.text)
+ if letter.printpage():
+ if dl.has_edits:
+ docsprinted.add(
+ dl.pt.serialno,
+ "%s(html)" % type,
+ dl.text,
+ version + 1
+ )
return True
def printReferral(om_gui):
- '''prints a referal letter controlled by referal.xml file'''
- # TODO this file should really be in the sql database
+ '''
+ prints a referal letter
+ '''
if om_gui.pt.serialno == 0:
om_gui.advise("no patient selected", 1)
return
desc = om_gui.ui.referralLettersComboBox.currentText()
- # todo re-enable this
- # if "Ortho" in desc:
- # orthoWizard(om_gui)
- # return
- html = referral.getHtml(desc, om_gui.pt)
- Dialog = QtGui.QDialog() # , QtCore.Qt.WindowMinimizeButtonHint)
- dl = Ui_enter_letter_text.Ui_Dialog()
- dl.setupUi(Dialog)
- dl.textEdit.setHtml(html)
- referred_pt = om_gui.pt
- Dialog.show()
- if Dialog.exec_():
- html = dl.textEdit.toHtml()
- myclass = letterprint.letter(html)
- myclass.printpage()
- docsprinted.add(referred_pt.serialno, "referral (html)", html)
- referred_pt.addHiddenNote("printed", "referral")
- om_gui.updateHiddenNotesLabel()
-
- if referred_pt == om_gui.pt:
- if om_gui.ui.prevCorres_treeWidget.isVisible():
- om_gui.docsPrintedInit()
- else:
- referred_pt.toNotes(referred_pt.serialno, referred_pt.HIDDENNOTES)
-
-def orthoWizard(om_gui):
- '''prints a referal letter controlled by referal.xml file'''
- desc = om_gui.ui.referralLettersComboBox.currentText()
html = referral.getHtml(desc, om_gui.pt)
+ dl = CorrespondenceDialog(html, om_gui.pt, preformatted=False, parent=None)
+ dl.show()
+ if dl.exec_():
+ letter = letterprint.letter(dl.text)
+ if letter.printpage():
+ docsprinted.add(dl.pt.serialno,
+ "%s referral (html)" % desc,
+ dl.text)
+ dl.pt.addHiddenNote("printed", "referral")
+
+ if dl.pt == om_gui.pt:
+ if om_gui.ui.prevCorres_treeWidget.isVisible():
+ om_gui.docsPrintedInit()
+ om_gui.updateHiddenNotesLabel()
+ else:
+ dl.pt.toNotes(dl.pt.serialno, dl.pt.HIDDENNOTES)
- Dialog = QtGui.QDialog(om_gui)
- dl = Ui_ortho_ref_wizard.Ui_Dialog()
- dl.setupUi(Dialog)
- dl.notes_textEdit.setHtml(html)
- if Dialog.exec_():
- html = dl.textEdit.toHtml()
- myclass = letterprint.letter(html)
- myclass.printpage()
- docsprinted.add(om_gui.pt.serialno, "referral (html)", html)
- om_gui.pt.addHiddenNote("printed", "referral")
- if om_gui.ui.prevCorres_treeWidget.isVisible():
- om_gui.docsPrintedInit()
- om_gui.updateHiddenNotesLabel()
+ return True
def printChart(om_gui):
@@ -572,3 +542,13 @@ def historyPrint(om_gui):
html = om_gui.ui.debugBrowser.toHtml()
myclass = bookprint.printBook(html)
myclass.printpage()
+
+
+if __name__ == "__main__":
+ import os
+ from openmolar.dbtools import patient_class
+ os.chdir(os.path.expanduser("~"))
+ app = QtGui.QApplication([])
+ widg = QtGui.QWidget()
+ widg.pt = patient_class.patient(1)
+ printLetter(widg)
diff --git a/src/openmolar/qt4gui/schema_updater.py b/src/openmolar/qt4gui/schema_updater.py
index 7bca50b..c133fc9 100644
--- a/src/openmolar/qt4gui/schema_updater.py
+++ b/src/openmolar/qt4gui/schema_updater.py
@@ -339,6 +339,20 @@ class SchemaUpdater(BaseDialog, Advisor):
self.dbu = upmod.DatabaseUpdater(self.pb)
self.apply_update()
+ # UPDATE TO SCHEMA 3.0 ########################
+ self.next_version = "3.0"
+ if self.current_version < self.next_version:
+ from openmolar.schema_upgrades import schema2_9to3_0 as upmod
+ self.dbu = upmod.DatabaseUpdater(self.pb)
+ self.apply_update()
+
+ # UPDATE TO SCHEMA 3.1 ########################
+ self.next_version = "3.1"
+ if self.current_version < self.next_version:
+ from openmolar.schema_upgrades import schema3_0to3_1 as upmod
+ self.dbu = upmod.DatabaseUpdater(self.pb)
+ self.apply_update()
+
self.dbu = None
if schema_version.getVersion() == localsettings.CLIENT_SCHEMA_VERSION:
self.success()
diff --git a/src/openmolar/resources/icons/med.png b/src/openmolar/resources/icons/med.png
new file mode 100644
index 0000000..c7ca390
Binary files /dev/null and b/src/openmolar/resources/icons/med.png differ
diff --git a/src/openmolar/schema_upgrades/druglist.py b/src/openmolar/schema_upgrades/druglist.py
new file mode 100644
index 0000000..4aaa5d0
--- /dev/null
+++ b/src/openmolar/schema_upgrades/druglist.py
@@ -0,0 +1,3173 @@
+DRUGLIST = [
+ '50:50 Ointment',
+ 'Abacavir Sulphate',
+ 'Abatacept',
+ 'Abciximab',
+ 'Abelcet',
+ 'Abidec Multivitamin Drops',
+ 'Abilify',
+ 'Acamprosate Calcium',
+ 'Acarbose',
+ 'Accolate',
+ 'Accupro',
+ 'Accuretic',
+ 'Acea',
+ 'Acebutolol',
+ 'Acebutolol Hydrochloride',
+ 'Aceclofenac',
+ 'Acemetacin',
+ 'Acenocoumarol',
+ 'Acepril',
+ 'Acetazolamide',
+ 'Acetic Acid Cough Linctus',
+ 'Acetylcholine Chloride',
+ 'Acetylcysteine',
+ 'Acezide',
+ 'Aciclovir',
+ 'Aciclovir Sodium',
+ 'Acipimox',
+ 'Acitretin',
+ 'Aclasta',
+ 'Acnamino',
+ 'Acnecide Gel',
+ 'Acnecide Wash',
+ 'Acnisal',
+ 'Acnocin',
+ 'Acrivastine',
+ 'Actifed',
+ 'Actilyse',
+ 'Activated Charcoal/Magnesium Hydroxide',
+ 'Actonel',
+ 'Actonel Combi',
+ 'Actonorm Gel',
+ 'Actos',
+ 'Actrapid',
+ 'Acular',
+ 'Acwy Vax',
+ 'Adalat',
+ 'Adalimumab',
+ 'Adapalene',
+ 'Adartrel',
+ 'Adcal',
+ 'Adcal D3',
+ 'Adcortyl',
+ 'Adefovir Dipivoxil',
+ 'Adenocor',
+ 'Adenosine',
+ 'Adenuric',
+ 'Adipine',
+ 'Adizem',
+ 'Adrenaline',
+ 'Adrenaline Acid Tartrate',
+ 'Advagraf',
+ 'Aerobec',
+ 'Afinitor',
+ 'Aggrastat',
+ 'Agomelatine',
+ 'Agrippal',
+ 'Aknemycin Plus',
+ 'Aldactide',
+ 'Aldactone',
+ 'Aldara',
+ 'Aldesleukin',
+ 'Aldioxa/Chloroxylenol',
+ 'Alemtuzumab',
+ 'Alendronate Sodium',
+ 'Alendronate Sodium/Colecalciferol',
+ 'Alfacalcidol',
+ 'Alfentanil Hydrochloride',
+ 'Alfuzosin Hydrochloride',
+ 'Alimemazine Tartrate',
+ 'Alimta',
+ 'Aliskiren',
+ 'Alitretinoin',
+ 'Alkeran Injection',
+ 'Alkeran Tablets',
+ 'Allantoin/Coal Tar Extract/Hydrocortisone',
+ 'Allantoin/Lidocaine',
+ 'Allegron',
+ 'Allopurinol',
+ 'Almogran',
+ 'Almond Oil/Arachis Oil/Camphor',
+ 'Almotriptan Hydrogen Malate',
+ 'Alomide',
+ 'Alphaderm',
+ 'Alphagan',
+ 'Alphanine',
+ 'Alphosyl Hc',
+ 'Alprazolam',
+ 'Alprostadil',
+ 'Altacite Plus',
+ 'Altargo',
+ 'Alteplase',
+ 'Alu-Cap Capsules',
+ 'Aluminium Hydroxide',
+ 'Aluminium Sulphate',
+ 'Alverine',
+ 'Amantadine Hydrochloride',
+ 'Amaryl',
+ 'Ambirix',
+ 'Ambisome',
+ 'Ambrisentan',
+ 'Amias',
+ 'Amikacin Sulphate',
+ 'Amikin',
+ 'Amilamont',
+ 'Amiloride',
+ 'Amiloride Hydrochloride',
+ 'Amiloride/Furosemide',
+ 'Amiloride/Hydrochlorothiazide',
+ 'Aminophylline',
+ 'Amiodarone',
+ 'Amiodarone Hydrochloride',
+ 'Amisulpride',
+ 'Amitriptyline',
+ 'Amlodipine',
+ 'Amlodipine Besilate/Valsartan',
+ 'Amlostin',
+ 'Ammonia',
+ 'Ammonia/Eucalyptus',
+ 'Amorolfine Hydrochloride',
+ 'Amoxicillin',
+ 'Amoxil',
+ 'Amoxil Injection',
+ 'Amphocil',
+ 'Amphotericin',
+ 'Amphotericin Phospholipid Complex',
+ 'Ampicillin',
+ 'Ampicillin Sodium/Flucloxacillin Sodium',
+ 'Amsacrine',
+ 'Amsidine',
+ 'Amylase/Lipase/Protease',
+ 'Anabact',
+ 'Anacal Rectal Ointment',
+ 'Anadin Extra Soluble Tablets',
+ 'Anadin Extra Tablets',
+ 'Anadin Ibuprofen',
+ 'Anadin Original',
+ 'Anadin Paracetamol',
+ 'Anafranil',
+ 'Anapen',
+ 'Anastrozole',
+ 'Ancotil',
+ 'Anectine',
+ 'Anexate',
+ 'Angeliq',
+ 'Angettes',
+ 'Angiox',
+ 'Angitil',
+ 'Anidulafungin',
+ 'Anise Oil/Menthol/Capsicum Tincture',
+ 'Antabuse',
+ 'Antazoline/Xylometazoline',
+ 'Antepsin',
+ 'Anthisan',
+ 'Anthisan Bite And Sting Cream',
+ 'Anugesic-Hc Cream',
+ 'Anugesic-Hc Suppository',
+ 'Anusol Cream',
+ 'Anusol Ointment',
+ 'Anusol Plus Hc',
+ 'Anusol Suppositories',
+ 'Apidra',
+ 'Apo-Go',
+ 'Apomorphine Hydrochloride',
+ 'Apraclonidine Hydrochloride',
+ 'Aprepitant',
+ 'Apresoline',
+ 'Aprinox',
+ 'Aprovel',
+ 'Aptivus',
+ 'Aquadrate',
+ 'Aranesp',
+ 'Arava',
+ 'Arcoxia',
+ 'Aredia',
+ 'Argipressin',
+ 'Aricept',
+ 'Arimidex',
+ 'Aripiprazole',
+ 'Arixtra',
+ 'Aromasin',
+ 'Arpicolin',
+ 'Arsenic Trioxide',
+ 'Artelac',
+ 'Artemether/Lumefantrine',
+ 'Arthrotec',
+ 'Arythmol',
+ 'Arzerra',
+ 'Asacol',
+ 'Asasantin',
+ 'Ascorbic Acid/Amylmetacresol/Dichlorobenzyl Alcohol',
+ 'Ascorbic Acid/Phenylephrine/Paracetamol',
+ 'Asmabec',
+ 'Asmanex',
+ 'Asmasal',
+ 'Aspirin',
+ 'Aspirin/Dipyridamole',
+ 'Aspirin/Paracetamol Dispersible Tablets',
+ 'Aspro Clear',
+ 'Atarax',
+ 'Atazanavir Sulphate',
+ 'Atenolol',
+ 'Atenolol/Chlortalidone',
+ 'Atenolol/Nifedipine',
+ 'Ativan',
+ 'Atorvastatin',
+ 'Atosiban Acetate',
+ 'Atovaquone/Proguanil Hydrochloride',
+ 'Atracurium Besilate',
+ 'Atriance',
+ 'Atripla',
+ 'Atropine',
+ 'Atropine Sulphate',
+ 'Atrovent',
+ 'Augmentin',
+ 'Augmentin Intravenous',
+ 'Augmentin-Duo',
+ 'Avamys',
+ 'Avastin',
+ 'Avaxim',
+ 'Avelox',
+ 'Avloclor',
+ 'Avodart',
+ 'Avonex',
+ 'Axid',
+ 'Axsain',
+ 'Azactam',
+ 'Azathioprine',
+ 'Azelaic Acid',
+ 'Azelastine Hydrochloride',
+ 'Azilect',
+ 'Azithromycin',
+ 'Azithromycin Dihydrate',
+ 'Azopt',
+ 'Aztreonam',
+ 'Baby Meltus Cough Linctus',
+ 'Baclofen',
+ 'Bactroban',
+ 'Balneum Bath Oil',
+ 'Balneum Plus Bath Oil',
+ 'Balneum Plus Cream',
+ 'Balsalazide Disodium',
+ 'Bambec',
+ 'Bambuterol',
+ 'Baraclude',
+ 'Baratol',
+ 'Basiliximab',
+ 'Baxan',
+ 'Bazetham',
+ 'Bazuka',
+ 'Bcg (Connaught Strain)',
+ 'Bcg (Tice Strain)',
+ 'Beclazone',
+ 'Beclometasone Dipropionate',
+ 'Becodisks',
+ 'Beconase',
+ 'Bedranol',
+ 'Beechams',
+ 'Begrivac',
+ 'Belimumab',
+ 'Bemiparin',
+ 'Bemiparin Sodium',
+ 'Benadryl',
+ 'Benadryl Skin Allergy Relief Cream',
+ 'Bendroflumethiazide',
+ 'Benefix',
+ 'Benlysta',
+ 'Benylin',
+ 'Benzalkonium Chloride',
+ 'Benzocaine 1% Spray',
+ 'Benzoyl Peroxide',
+ 'Benzydamine Cream',
+ 'Benzydamine Mouthwash',
+ 'Benzydamine Oral Spray',
+ 'Benzylpenicillin Sodium',
+ 'Beta-Adalat',
+ 'Beta-Cardone',
+ 'Beta-Prograne',
+ 'Betacap',
+ 'Betadine Dry Powder Spray',
+ 'Betaferon',
+ 'Betagan',
+ 'Betahistine Dihydrochloride',
+ 'Betaloc',
+ 'Betamethasone Dipropionate',
+ 'Betamethasone Valerate',
+ 'Betaxolol Hydrochloride',
+ 'Bethanechol Chloride',
+ 'Betim',
+ 'Betnovate',
+ 'Betoptic',
+ 'Bettamousse',
+ 'Bevacizumab',
+ 'Bexarotene',
+ 'Bezafibrate',
+ 'Bezalip',
+ 'Bi-Carzem',
+ 'Bicalutamide',
+ 'Bicnu',
+ 'Bimatoprost',
+ 'Binovum',
+ 'Biorphen',
+ 'Bisacodyl Suppositories',
+ 'Bisacodyl Tablets',
+ 'Bismuth Subsalicylate',
+ 'Bisodol Indigestion Relief Tablets',
+ 'Bisoprolol Fumarate',
+ 'Bivalirudin',
+ 'Bleo-Kyowa',
+ 'Bleomycin Sulphate',
+ 'Blistex Relief Cream',
+ 'Bondronat',
+ 'Bonefos',
+ 'Bonjela',
+ 'Bonviva',
+ 'Bortezomib',
+ 'Bosentan',
+ 'Botox',
+ 'Brevinor',
+ 'Brevoxyl Cream',
+ 'Bricanyl',
+ 'Bridion',
+ 'Brilique',
+ 'Brimonidine Tartrate',
+ 'Brinzolamide',
+ 'Brochlor',
+ 'Broflex',
+ 'Bromocriptine Mesilate',
+ 'Brufen',
+ 'Buccastem',
+ 'Budesonide',
+ 'Bumetanide',
+ 'Bupivacaine Hydrochloride',
+ 'Bupropion Hydrochloride',
+ 'Burinex',
+ 'Burneze Spray',
+ 'Buscopan',
+ 'Buserelin Acetate',
+ 'Busilvex',
+ 'Buspar',
+ 'Buspirone',
+ 'Busulfan',
+ 'Buttercup Max Strength Sore Throat Lozenge',
+ 'Buttercup Syrup',
+ 'Bydureon',
+ 'Byetta',
+ 'Cabaser',
+ 'Cabergoline',
+ 'Cabren',
+ 'Cacit',
+ 'Caelyx',
+ 'Calaband',
+ 'Calceos',
+ 'Calchan',
+ 'Calcicard',
+ 'Calcichew',
+ 'Calcijex',
+ 'Calcipotriol',
+ 'Calcitonin (Salmon)',
+ 'Calcitriol',
+ 'Calcium Acetate',
+ 'Calcium Carbonate Antacids',
+ 'Calcium Carbonate Supplements',
+ 'Calcium Folinate',
+ 'Calcium Lactate',
+ 'Calcium Levofolinate',
+ 'Calcium Phosphate/Colecalciferol',
+ 'Calcium-Sandoz Syrup',
+ 'Calcold Six Plus',
+ 'Calcort',
+ 'Calfovit D3',
+ 'Calmurid Hc',
+ 'Calpol',
+ 'Calprofen',
+ 'Camcolit',
+ 'Campto',
+ 'Cancidas',
+ 'Candesartan',
+ 'Canesten',
+ 'Canusal',
+ 'Capecitabine',
+ 'Capoten',
+ 'Capozide',
+ 'Capreomycin Sulphate',
+ 'Caprin',
+ 'Capsaicin',
+ 'Captopril',
+ 'Captopril/Hydrochlorothiazide',
+ 'Carace Plus',
+ 'Caramet',
+ 'Carbamazepine',
+ 'Carbetocin',
+ 'Carbidopa Monohydrate/Levodopa',
+ 'Carbidopa/Entacapone/Levodopa',
+ 'Carbimazole',
+ 'Carbo-Dome Cream',
+ 'Carbocisteine',
+ 'Carbomer',
+ 'Carbomer Eye Drops',
+ 'Carboplatin',
+ 'Carboprost Trometamol',
+ 'Cardene',
+ 'Cardicor',
+ 'Cardioplen',
+ 'Cardioxane',
+ 'Cardura',
+ 'Carmellose Sodium',
+ 'Carmustine',
+ 'Casodex',
+ 'Caspofungin Acetate',
+ 'Catapres',
+ 'Caverject',
+ 'Cayston',
+ 'Ceanel',
+ 'Cedocard',
+ 'Cefaclor Monohydrate',
+ 'Cefadroxil Monohydrate',
+ 'Cefalexin',
+ 'Cefixime',
+ 'Cefotaxime Sodium',
+ 'Cefpodoxime Proxetil',
+ 'Ceftazidime Pentahydrate',
+ 'Ceftriaxone Sodium',
+ 'Cefuroxime Axetil',
+ 'Cefuroxime Sodium',
+ 'Celance',
+ 'Celebrex',
+ 'Celecoxib',
+ 'Celectol',
+ 'Celevac Tablets',
+ 'Celiprolol',
+ 'Celiprolol Hydrochloride',
+ 'Cellcept',
+ 'Cellcept Powder',
+ 'Celluvisc',
+ 'Celsentri',
+ 'Ceplene',
+ 'Ceporex',
+ 'Cerazette',
+ 'Certolizumab Pegol',
+ 'Cervarix',
+ 'Cetirizine Liquid',
+ 'Cetirizine Tablets',
+ 'Cetraben Bath Additive',
+ 'Cetraben Emollient Cream',
+ 'Cetrimide',
+ 'Cetrimide/Benzalkonium Chloride',
+ 'Cetrorelix Acetate',
+ 'Cetrotide',
+ 'Cetuximab',
+ 'Cetylpyridinium Chloride Lozenges',
+ 'Cetylpyridinium Chloride/Menthol',
+ 'Champix',
+ 'Chemydur',
+ 'Chirocaine',
+ 'Chlorambucil',
+ 'Chloramphenicol',
+ 'Chloramphenicol Sodium Succinate',
+ 'Chlordiazepoxide',
+ 'Chlordiazepoxide Hydrochloride',
+ 'Chlorobutanol/Arachis Oil',
+ 'Chlorobutanol/Lidocaine/Alcloxa/Cetrimide',
+ 'Chlorocresol/Urea/Cetrimide/Dimeticone',
+ 'Chloromycetin',
+ 'Chloroquine',
+ 'Chloroxylenol',
+ 'Chlorphenamine Maleate',
+ 'Chlorpromazine Hydrochloride',
+ 'Chlortalidone',
+ 'Cholestagel',
+ 'Choline Salicylate',
+ 'Choragon',
+ 'Choriogonadotropin Alfa',
+ 'Chorionic Gonadotrophin Human',
+ 'Cialis',
+ 'Cibral',
+ 'Cicafem',
+ 'Ciclesonide',
+ 'Ciclosporin',
+ 'Cidofovir',
+ 'Cilastatin Sodium/Imipenem Monohydrate',
+ 'Cilazapril',
+ 'Cilest',
+ 'Cilostazol',
+ 'Cimetidine',
+ 'Cimzia',
+ 'Cinacalcet Hydrochloride',
+ 'Cinchocaine',
+ 'Cinnarizine',
+ 'Cipralex',
+ 'Cipramil',
+ 'Cipramil Drops',
+ 'Ciprofibrate',
+ 'Ciprofloxacin',
+ 'Ciprofloxacin Hydrochloride',
+ 'Ciprofloxacin Lactate',
+ 'Ciproxin Injection',
+ 'Ciproxin Suspension',
+ 'Ciproxin Tablets',
+ 'Circadin',
+ 'Cisatracurium Besilate',
+ 'Cisplatin',
+ 'Citalopram Hydrobromide',
+ 'Citalopram Hydrochloride',
+ 'Citanest',
+ 'Citric Acid/Ipecacuanha',
+ 'Cladribine',
+ 'Claforan',
+ 'Clairette',
+ 'Clarelux',
+ 'Clarie Xl',
+ 'Clarithromycin',
+ 'Clemastine',
+ 'Clenil Modulite',
+ 'Clexane',
+ 'Climagest',
+ 'Climaval',
+ 'Climesse',
+ 'Clindamycin Hydrochloride',
+ 'Clindamycin Phosphate',
+ 'Clioquinol/Flumetasone Pivalate',
+ 'Clobazam',
+ 'Clobetasol Propionate',
+ 'Clobetasone Butyrate',
+ 'Clofarabine',
+ 'Clomethiazole',
+ 'Clomid',
+ 'Clomifene Citrate',
+ 'Clomipramine',
+ 'Clonazepam',
+ 'Clonidine',
+ 'Clopamide/Pindolol',
+ 'Clopidogrel',
+ 'Clopixol Acuphase',
+ 'Clopixol Conc',
+ 'Clopixol Tablets',
+ 'Clotam',
+ 'Clotrimazole',
+ 'Clozapine',
+ 'Clozaril',
+ 'Co-Beneldopa',
+ 'Co-Careldopa',
+ 'Co-Danthramer',
+ 'Co-Diovan',
+ 'Co-Fluampicil',
+ 'Co-Magaldrox',
+ 'Co-Simalcite',
+ 'Co-Trimoxazole',
+ 'Co-Zidocapt',
+ 'Coaprovel',
+ 'Cobalin-H',
+ 'Codeine/Paracetamol',
+ 'Colazide',
+ 'Colchicine',
+ 'Colecalciferol/Calcium Carbonate',
+ 'Colesevelam Hydrochloride',
+ 'Colestid',
+ 'Colestipol',
+ 'Colestipol Hydrochloride',
+ 'Colestyramine Anhydrous',
+ 'Colifoam',
+ 'Colistimethate Sodium',
+ 'Colistin Sulphate',
+ 'Colofac',
+ 'Colomycin',
+ 'Colomycin Injection',
+ 'Colomycin Syrup',
+ 'Colomycin Tablet',
+ 'Colpermin Ibs Relief Capsules',
+ 'Combigan',
+ 'Combivent',
+ 'Combivir',
+ 'Combodart',
+ 'Competact',
+ 'Compound W',
+ 'Comtess',
+ 'Concerta Xl',
+ 'Condyline',
+ 'Conjugated Oestrogens',
+ 'Copaxone',
+ 'Copegus',
+ 'Coracten',
+ 'Cordarone X',
+ 'Cordilox',
+ 'Corgard',
+ 'Corifollitropin Alfa',
+ 'Cortisone Acetate',
+ 'Cosmofer',
+ 'Cosopt',
+ 'Coversyl Arginine',
+ 'Coversyl Arginine Plus',
+ 'Cozaar',
+ 'Cozaar-Comp',
+ 'Cream Of Magnesia Tablets',
+ 'Creon',
+ 'Crestor',
+ 'Crinone',
+ 'Cromogen',
+ 'Crotamiton',
+ 'Cubicin',
+ 'Cuprofen',
+ 'Cuprofen Plus',
+ 'Cutivate',
+ 'Cyanocobalamin',
+ 'Cyclizine Lactate',
+ 'Cyclo-Progynova 2Mg',
+ 'Cyclopentolate Hydrochloride',
+ 'Cyclophosphamide Monohydrate',
+ 'Cycloserine',
+ 'Cyklokapron',
+ 'Cymalon',
+ 'Cymbalta',
+ 'Cymevene',
+ 'Cymex Cream',
+ 'Cyproheptadine',
+ 'Cyprostat',
+ 'Cyproterone Acetate',
+ 'Cyproterone Acetate/Ethinylestradiol',
+ 'Cystrin',
+ 'Cytamen',
+ 'Cytarabine',
+ 'Cytotec',
+ 'D-Gam',
+ 'Dabigatran Etexilate Mesilate',
+ 'Dacarbazine Citrate',
+ 'Daktacort',
+ 'Daktarin Oral Gel',
+ 'Dalacin',
+ 'Dalacin C',
+ 'Dalmane',
+ 'Dalteparin',
+ 'Danaparoid',
+ 'Danazol',
+ 'Dandrazol',
+ 'Danol',
+ 'Dantrium',
+ 'Dantrolene Sodium',
+ 'Daptomycin',
+ 'Daraprim',
+ 'Darbepoetin Alfa',
+ 'Darifenacin Hydrobromide',
+ 'Darunavir Ethanolate',
+ 'Dasatinib',
+ 'Daunorubicin Hydrochloride Citrate',
+ 'Daunoxome',
+ 'Daxas',
+ 'Day Nurse',
+ 'Day Nurse Capsules',
+ 'Ddavp',
+ 'Deca-Durabolin',
+ 'Decapeptyl',
+ 'Defanac',
+ 'Deferasirox',
+ 'Deferiprone',
+ 'Deflazacort',
+ 'Deltacortril',
+ 'Deltastab',
+ 'Demeclocycline Hydrochloride',
+ 'Denosumab',
+ 'Denzapine',
+ 'Depakote',
+ 'Depixol',
+ 'Depixol Tablets',
+ 'Depo-Medrone',
+ 'Depo-Medrone With Lidocaine',
+ 'Depo-Provera',
+ 'Depocyte',
+ 'Dequacaine',
+ 'Dequadin',
+ 'Dequalinium',
+ 'Derbac M',
+ 'Dermalo Bath Emollient',
+ 'Dermamist',
+ 'Dermax',
+ 'Dermidex',
+ 'Dermovate',
+ 'Deseril',
+ 'Desferal',
+ 'Desferrioxamine Mesilate',
+ 'Desloratadine',
+ 'Desmomelt',
+ 'Desmopressin Acetate',
+ 'Desmospray',
+ 'Desmotabs',
+ 'Desogestrel',
+ 'Destolit',
+ 'Detrunorm',
+ 'Detrusitol',
+ 'Dexamethasone',
+ 'Dexibuprofen',
+ 'Dexketoprofen Trometamol',
+ 'Dexomon',
+ 'Dexrazoxane',
+ 'Dexsol',
+ 'Dextromethorphan',
+ 'Dextromethorphan/Menthol',
+ 'Dextromethorphan/Pseudoephedrine',
+ 'Dhc Continus',
+ 'Diamicron',
+ 'Diamorphine',
+ 'Diamox',
+ 'Dianette',
+ 'Diazemuls',
+ 'Diazepam',
+ 'Dichlorobenzyl Alcohol/Amylmetacresol',
+ 'Diclofenac Diethylammonium',
+ 'Diclofenac Epolamine',
+ 'Diclofenac Potassium',
+ 'Diclofenac Sodium',
+ 'Diclofenac Sodium/Misoprostol',
+ 'Dicloflex',
+ 'Diclomax',
+ 'Dicycloverine Hydrochloride',
+ 'Dicynene',
+ 'Didanosine',
+ 'Didronel',
+ 'Didronel Pmo',
+ 'Diethylamine Salicylate',
+ 'Differin',
+ 'Difflam Cream',
+ 'Difflam Solution',
+ 'Difflam Spray',
+ 'Diflucan',
+ 'Digoxin',
+ 'Dihydrocodeine Tartrate',
+ 'Dilcardia',
+ 'Dill Oil/Sodium Bicarbonate/Ginger',
+ 'Diloxanide Furoate',
+ 'Diltiazem',
+ 'Diltiazem Hydrochloride',
+ 'Dilzem',
+ 'Dimeticone',
+ 'Dinoprostone',
+ 'Diocalm',
+ 'Dioctyl',
+ 'Dioderm',
+ 'Dioralyte',
+ 'Dioralyte Relief',
+ 'Diovan',
+ 'Dipentum',
+ 'Diphenhydramine',
+ 'Dipivefrine Hydrochloride',
+ 'Diprivan',
+ 'Diprobase Cream',
+ 'Diprobase Ointment',
+ 'Diprobath',
+ 'Diprosalic',
+ 'Diprosone',
+ 'Dipyridamole',
+ 'Disipal',
+ 'Disodium Etidronate',
+ 'Disodium Folinate',
+ 'Disodium Pamidronate',
+ 'Disopyramide',
+ 'Disopyramide Phosphate',
+ 'Disprin',
+ 'Disprin Extra',
+ 'Distaclor',
+ 'Distamine',
+ 'Distigmine Bromide',
+ 'Distilled Witch Hazel',
+ 'Disulfiram',
+ 'Dithranol',
+ 'Dithrocream',
+ 'Ditropan',
+ 'Diurexan',
+ 'Dixarit',
+ 'Do-Do Chesteze',
+ 'Dobutamine Hydrochloride',
+ 'Docetaxel',
+ 'Docusate',
+ 'Docusate Gel Enema',
+ 'Docusate Sodium Ear Drops',
+ 'Dolmatil',
+ 'Domperidone',
+ 'Domperidone Maleate',
+ 'Dopamine',
+ 'Dopamine Hydrochloride',
+ 'Dopexamine',
+ 'Doralese Tiltab',
+ 'Doribax',
+ 'Doripenem Monohydrate',
+ 'Dornase Alfa',
+ 'Dorzolamide Hydrochloride',
+ 'Dorzolamide Hydrochloride/Timolol Maleate',
+ 'Doublebase Bath Additive',
+ 'Doublebase Gel',
+ 'Doublebase Shower Gel',
+ 'Doublebase Wash Gel',
+ 'Dovobet',
+ 'Dovonex',
+ 'Dovonex Cream',
+ 'Doxadura',
+ 'Doxazosin',
+ 'Doxazosin Mesilate',
+ 'Doxorubicin Hydrochloride',
+ 'Doxorubin',
+ 'Doxycycline Hyclate',
+ 'Doxycycline Monohydrate',
+ 'Doxylamine/Pseudoephedrine/Dextromethorphan/Paracetamol',
+ 'Doxylar',
+ 'Dozic',
+ 'Drapolene Cream',
+ 'Dromadol',
+ 'Dronedarone Hydrochloride',
+ 'Drospirenone/Estradiol Hemihydrate',
+ 'Drospirenone/Ethinylestradiol',
+ 'Drotrecogin Alfa',
+ 'Droxia',
+ 'Duac',
+ 'Dukoral',
+ 'Dulcobalance',
+ 'Dulcoease',
+ 'Dulcolax Suppositories',
+ 'Dulcolax Tablets',
+ 'Duloxetine',
+ 'Duloxetine Hydrochloride',
+ 'Duodopa',
+ 'Duofilm',
+ 'Duotrav',
+ 'Duovent',
+ 'Dutasteride',
+ 'Dydrogesterone/Estradiol',
+ 'Dysport',
+ 'E45 Cream',
+ 'E45 Itch Relief Cream',
+ 'Earex Ear Drops',
+ 'Earex Plus',
+ 'Easyhaler Beclometasone',
+ 'Easyhaler Budesonide',
+ 'Easyhaler Formoterol',
+ 'Easyhaler Salbutamol',
+ 'Ebixa',
+ 'Ecalta',
+ 'Eccoxolac',
+ 'Econac',
+ 'Econazole Nitrate',
+ 'Ecopace',
+ 'Eczmol',
+ 'Edronax',
+ 'Edrophonium Chloride',
+ 'Efavirenz',
+ 'Efcortesol',
+ 'Efexor',
+ 'Efient',
+ 'Eflornithine Monohydrate Chloride',
+ 'Efudix',
+ 'Eldepryl',
+ 'Electrolade',
+ 'Eletriptan Hydrobromide',
+ 'Elidel',
+ 'Ellaone',
+ 'Elleste Solo',
+ 'Ellimans Universal Muscle Rub Lotion',
+ 'Elocon',
+ 'Elonva',
+ 'Eloxatin',
+ 'Eludril Mouthwash',
+ 'Eludril Spray',
+ 'Emadine',
+ 'Emcor',
+ 'Emedastine Difumarate',
+ 'Emend',
+ 'Emeside',
+ 'Emflex',
+ 'Emla',
+ 'Emselex',
+ 'Emtricitabine',
+ 'Emtricitabine/Tenofovir Disoproxil Fumarate',
+ 'Emtriva',
+ 'Emulsiderm Emollient',
+ 'Enalapril',
+ 'Enalapril/Hydrochlorothiazide',
+ 'Enbrel',
+ 'Endoxana',
+ 'Enfuvirtide',
+ 'Engerix B',
+ 'Eno',
+ 'Enoxaparin',
+ 'Enoxaparin Sodium',
+ 'Entacapone',
+ 'Entecavir Monohydrate',
+ 'Entocort',
+ 'Enzira',
+ 'Epanutin Capsules',
+ 'Epanutin Infatabs',
+ 'Epanutin Ready Mixed Parenteral',
+ 'Epanutin Suspension',
+ 'Epaxal',
+ 'Ephedrine Hydrochloride',
+ 'Ephedrine Hydrochloride/Chlorphenamine',
+ 'Ephedrine Nasal Drops',
+ 'Epilim',
+ 'Epinastine Hydrochloride',
+ 'Epipen',
+ 'Epirubicin Hydrochloride',
+ 'Epival',
+ 'Epivir',
+ 'Eplerenone',
+ 'Epoetin Alfa',
+ 'Epoetin Beta',
+ 'Epoprostenol',
+ 'Eposin',
+ 'Eprex',
+ 'Eprosartan',
+ 'Eprosartan Mesilate',
+ 'Epsom Salts',
+ 'Eptacog Alfa',
+ 'Eptifibatide',
+ 'Erbitux',
+ 'Erdosteine',
+ 'Erdotin',
+ 'Ergometrine Maleate',
+ 'Ergometrine Maleate/Oxytocin',
+ 'Erlotinib Hydrochloride',
+ 'Ertapenem Sodium',
+ 'Eryacne 4',
+ 'Erymax',
+ 'Erythrocin',
+ 'Erythromycin',
+ 'Erythromycin Ethyl Succinate',
+ 'Erythromycin Lactobionate',
+ 'Erythromycin Stearate',
+ 'Erythromycin/Isotretinoin',
+ 'Erythromycin/Tretinoin',
+ 'Erythromycin/Zinc Acetate',
+ 'Erythroped',
+ 'Escitalopram',
+ 'Escitalopram Oxalate',
+ 'Esmeron',
+ 'Esmya',
+ 'Esomeprazole Injection',
+ 'Esomeprazole Tablets',
+ 'Estracyt',
+ 'Estraderm',
+ 'Estradiol',
+ 'Estradiol Hemihydrate',
+ 'Estradiol Hemihydrate/Norethisterone Acetate',
+ 'Estradiol Valerate',
+ 'Estradiol Valerate/Medroxyprogesterone',
+ 'Estradiol Valerate/Medroxyprogesterone Acetate',
+ 'Estradiol Valerate/Norethisterone',
+ 'Estradiol Valerate/Norgestrel',
+ 'Estradiol/Estriol/Estrone',
+ 'Estradiol/Levonorgestrel',
+ 'Estradiol/Norethisterone Acetate',
+ 'Estradot',
+ 'Estramustine Sodium Phosphate',
+ 'Estring',
+ 'Estriol',
+ 'Estropipate',
+ 'Etamsylate',
+ 'Etanercept',
+ 'Ethanolamine',
+ 'Ethinylestradiol',
+ 'Ethinylestradiol/Etonogestrel',
+ 'Ethinylestradiol/Gestodene',
+ 'Ethinylestradiol/Levonorgestrel',
+ 'Ethinylestradiol/Norelgestromin',
+ 'Ethinylestradiol/Norethisterone',
+ 'Ethinylestradiol/Norethisterone Acetate',
+ 'Ethinylestradiol/Norgestimate',
+ 'Ethosuximide',
+ 'Etodolac',
+ 'Etomidate',
+ 'Etonogestrel',
+ 'Etopophos',
+ 'Etoposide',
+ 'Etoposide Phosphate',
+ 'Etoricoxib',
+ 'Etravirine',
+ 'Etynodiol Diacetate',
+ 'Eucalyptus/Menthol/Cetylpyridinium Chloride',
+ 'Eucalyptus/Terpineol/Methyl Salicylate/Menthol/Camphor',
+ 'Eucalyptus/Thyme/Menthol',
+ 'Eucalyptus/Turpentine/Levomenthol/Camphor',
+ 'Eucalyptus/Turpentine/Methyl Salicylate/Menthol',
+ 'Eucreas',
+ 'Eumocream',
+ 'Eumovate',
+ 'Eurax',
+ 'Eurax Hydrocortisone',
+ 'Everolimus',
+ 'Evista',
+ 'Evoltra',
+ 'Evorel',
+ 'Evorel Conti',
+ 'Evorel Sequi',
+ 'Evra',
+ 'Ex-Lax Senna',
+ 'Exelon',
+ 'Exelon Patches',
+ 'Exemestane',
+ 'Exenatide',
+ 'Exforge',
+ 'Exjade',
+ 'Exocin',
+ 'Exorex Lotion',
+ 'Extavia',
+ 'Exterol',
+ 'Ezetimibe',
+ 'Ezetimibe/Simvastatin',
+ 'Ezetrol',
+ 'Factor Ii/Factor Vii/Protein S/Factor X/Protein C/Factor Ix',
+ 'Factor Ix High Purity',
+ 'Factor Viii/Von Willebrand Factor',
+ 'Factor Xiii',
+ 'Famciclovir',
+ 'Family Meltus Chesty Coughs',
+ 'Famotidine',
+ 'Famotidine/Calcium Carbonate/Magnesium Hydroxide',
+ 'Famvir',
+ 'Fareston',
+ 'Fasigyn',
+ 'Faslodex',
+ 'Faverin',
+ 'Febuxostat',
+ 'Felbinac',
+ 'Feldene',
+ 'Felendil',
+ 'Felodipine',
+ 'Felodipine/Ramipril',
+ 'Felogen',
+ 'Felotens',
+ 'Femapak',
+ 'Femara',
+ 'Fematrix',
+ 'Femodene',
+ 'Femodette',
+ 'Femoston',
+ 'Femseven Conti',
+ 'Femseven Patches',
+ 'Femseven Sequi',
+ 'Femulen',
+ 'Fenactol',
+ 'Fendrix',
+ 'Fenistil',
+ 'Fenofibrate',
+ 'Fenofibrate Micronised',
+ 'Fenoterol/Ipratropium',
+ 'Fenticonazole Nitrate',
+ 'Ferinject',
+ 'Ferric Carboxymaltose',
+ 'Ferriprox',
+ 'Ferrous Fumarate',
+ 'Ferrous Fumarate/Folic Acid',
+ 'Ferrous Sulphate Tablets',
+ 'Ferrous Sulphate/Ascorbic Acid',
+ 'Fersaday',
+ 'Fersamal',
+ 'Fesoterodine Fumarate',
+ 'Fexofenadine Hydrochloride',
+ 'Fibrazate',
+ 'Fibro-Vein',
+ 'Fibrogammin P',
+ 'Filgrastim',
+ 'Finacea',
+ 'Finasteride',
+ 'Fingolimod Hydrochloride',
+ 'Flagyl',
+ 'Flagyl-S',
+ 'Flamatak',
+ 'Flamrase',
+ 'Flavoxate Hydrochloride',
+ 'Flecainide',
+ 'Flecainide Acetate',
+ 'Flexin',
+ 'Flixonase',
+ 'Flixonase Allergy',
+ 'Flixotide',
+ 'Flolan',
+ 'Flomaxtra',
+ 'Florinef',
+ 'Floxapen',
+ 'Fluanxol',
+ 'Fluarix',
+ 'Flucloxacillin Sodium',
+ 'Fluconazole',
+ 'Fluconazole And Clotrimazole',
+ 'Flucytosine',
+ 'Fludara',
+ 'Fludarabine Phosphate',
+ 'Fludrocortisone Acetate',
+ 'Fluimucil N',
+ 'Flumazenil',
+ 'Fluorescein Sodium/Lidocaine Hydrochloride',
+ 'Fluorescein Sodium/Proxymetacaine Hydrochloride',
+ 'Fluorometholone',
+ 'Fluorouracil',
+ 'Fluorouracil Sodium',
+ 'Fluoxetine',
+ 'Flupentixol',
+ 'Flupentixol Decanoate',
+ 'Flupentixol Dihydrochloride',
+ 'Fluphenazine Decanoate',
+ 'Flurazepam',
+ 'Flurazepam Hydrochloride',
+ 'Flurbiprofen',
+ 'Flurbiprofen Sodium',
+ 'Fluticasone Furoate',
+ 'Fluticasone Propionate',
+ 'Fluticasone/Salmeterol',
+ 'Fluvastatin',
+ 'Fluvastatin Sodium',
+ 'Fluvirin',
+ 'Fluvoxamine',
+ 'Fluvoxamine Maleate',
+ 'Fml',
+ 'Folic Acid',
+ 'Follitropin Alfa',
+ 'Follitropin Alfa/Lutropin Alfa',
+ 'Follitropin Beta',
+ 'Fondaparinux',
+ 'Foradil',
+ 'Forceval',
+ 'Formaldehyde',
+ 'Formoterol Fumarate Dihydrate',
+ 'Forsteo',
+ 'Fortipine',
+ 'Fortum',
+ 'Fosamax',
+ 'Fosamprenavir Calcium',
+ 'Fosavance',
+ 'Foscarnet Sodium',
+ 'Foscavir',
+ 'Fosinopril',
+ 'Fosphenytoin Sodium',
+ 'Fosrenol',
+ 'Fostair',
+ 'Fragmin',
+ 'Freederm Gel',
+ 'Frisium',
+ 'Froben',
+ 'Frovatriptan Succinate Monohydrate',
+ 'Fru-Co',
+ 'Frumil',
+ 'Frusene',
+ 'Frusol',
+ 'Fucibet',
+ 'Fucidin Cream',
+ 'Fucidin H',
+ 'Fucidin H Ointment',
+ 'Fucidin Ointment',
+ 'Fucidin Suspension',
+ 'Fucidin Tablets',
+ 'Fucithalmic',
+ 'Fulvestrant',
+ 'Fungizone',
+ 'Furadantin',
+ 'Furosemide',
+ 'Furosemide/Spironolactone',
+ 'Furosemide/Triamterene',
+ 'Fusidic Acid',
+ 'Fusidic Acid/Hydrocortisone Acetate',
+ 'Fuzeon',
+ 'Fybogel',
+ 'Fybogel Mebeverine',
+ 'Gabapentin',
+ 'Gabitril',
+ 'Gadobutrol',
+ 'Gadoteridol',
+ 'Gadovist',
+ 'Gadoxetate Disodium',
+ 'Galantamine Hydrobromide',
+ 'Galvus',
+ 'Gamanil',
+ 'Ganciclovir Sodium',
+ 'Ganfort',
+ 'Ganirelix',
+ 'Gardasil',
+ 'Garlic Oil/Garlic/Echinacea',
+ 'Gastrobid',
+ 'Gastrocote Liquid',
+ 'Gastrocote Tablets',
+ 'Gaviscon Advance',
+ 'Gaviscon Cool Tablets',
+ 'Gaviscon Double Action',
+ 'Gaviscon Extra Strength Tablets',
+ 'Gaviscon Infant Oral Powder',
+ 'Gaviscon Liquid Sachets',
+ 'Gaviscon Tablets',
+ 'Gefitinib',
+ 'Geltears',
+ 'Gemcitabine Hydrochloride',
+ 'Gemeprost',
+ 'Gemfibrozil',
+ 'Gemzar',
+ 'Generic Abidec Multivitamin Drops',
+ 'Generic Actonorm Powder',
+ 'Generic Alginate/Aluminium Hydroxide/Magnesium Carbonate',
+ 'Generic Antitis Tablets',
+ 'Generic Calcimax Liquid',
+ 'Generic Catarrh Relief Mixture',
+ 'Generic Cetanorm Cream',
+ 'Generic Chest Mixture',
+ 'Generic Cymalon Granules',
+ 'Generic Dalivit Oral Drops',
+ 'Generic Deep Heat Spray',
+ 'Generic Diocalm',
+ 'Generic Dioralyte Powder',
+ 'Generic Dioralyte Relief',
+ 'Generic Diprobase Cream',
+ 'Generic Dubam Cream',
+ 'Generic Dulbalm Cream',
+ 'Generic Electrolade Powder',
+ 'Generic Fiery Jack Cream',
+ 'Generic Forceval Capsules',
+ 'Generic Germolene Antiseptic Ointment',
+ 'Generic Glycerin, Honey And Lemon Linctus',
+ 'Generic Indian Brandee',
+ 'Generic Karvol Decongestant Capsules',
+ 'Generic Karvol Decongestant Drops',
+ 'Generic Ketovite Liquid',
+ 'Generic Laxido',
+ 'Generic Lipobase',
+ 'Generic Metanium Barrier Ointment',
+ 'Generic Meted Shampoo',
+ 'Generic Molaxole Powder',
+ 'Generic Oxymetazoline',
+ 'Generic Pharmaton Vitality Capsules',
+ 'Generic Phytex',
+ 'Generic Polytar Af Liquid',
+ 'Generic Polytar Emollient',
+ 'Generic Polytar Liquid',
+ 'Generic Polytar Plus Liquid',
+ "Generic Potter'S Strong Bronchial Catarrh Pastilles",
+ "Generic Potter'S Sugar Free Cough Pastilles",
+ 'Generic Radian B Muscle Lotion',
+ 'Generic Rehydration Powder',
+ 'Generic Resolve Effervescent Granules',
+ 'Generic Sciargo Tablets',
+ 'Generic Senokot Comfort Tablets',
+ 'Generic Senokot Dual Relief Tablets',
+ 'Generic Sudocrem Cream',
+ 'Generic Tcp Antiseptic Cream',
+ 'Generic Tcp Antiseptic Ointment',
+ 'Generic Throaties Strong Original Pastilles',
+ 'Generic Transvasin Spray',
+ 'Generic Ultrabase',
+ 'Generic Unguentum M Cream',
+ 'Generic Vadarex Ointment',
+ 'Generic Vegetable Cough Remover Elixir',
+ 'Genotropin',
+ 'Gentamicin Sulphate',
+ 'Gentamicin Sulphate/Hydrocortisone Acetate',
+ 'Gentisone',
+ 'Germolene Antiseptic Cream',
+ 'Germolene Antiseptic First Aid Wash',
+ 'Germolene Antiseptic Ointment',
+ 'Germoloids Cream',
+ 'Germoloids Duo Pack',
+ 'Germoloids Hc Spray',
+ 'Germoloids Ointment',
+ 'Germoloids Suppositories',
+ 'Gestone',
+ 'Gestrinone',
+ 'Gilenya',
+ 'Glatiramer Acetate',
+ 'Gliadel',
+ 'Glibenese',
+ 'Gliclazide',
+ 'Glimepiride',
+ 'Glipizide',
+ 'Glivec',
+ 'Glucagen',
+ 'Glucagon',
+ 'Glucobay',
+ 'Glucophage',
+ 'Glucose Anhydrous',
+ 'Glucose/Treacle Cough Mixture',
+ 'Glutaraldehyde',
+ 'Glutarol',
+ 'Glycerin And Blackcurrant Cough Syrup',
+ 'Glycerin, Honey And Lemon Linctus',
+ 'Glycerin, Honey, Lemon And Ipecacuanha Linctus',
+ 'Glycerol Cream',
+ 'Glycerol Oral Solution',
+ 'Glycerol Skin Wash',
+ 'Glycerol/Glucose Cough Mixture',
+ 'Glycerol/Sucrose Cough Mixture',
+ 'Glycerol/Syrup/Citric Acid/Honey/Lemon',
+ 'Glycerol/White Soft Paraffin/Liquid Paraffin',
+ 'Glyceryl Trinitrate',
+ 'Glycopyrronium Bromide',
+ 'Goddards Muscle Lotion',
+ 'Golimumab',
+ 'Gonapeptyl',
+ 'Gopten',
+ 'Goserelin Acetate',
+ 'Granisetron Hydrochloride',
+ 'Granocyte-13',
+ 'Grass Pollen Extract',
+ 'Grazax',
+ 'Griseofulvin',
+ 'Grisol Af',
+ 'Guaiacol/Codeine',
+ 'Guaifenesin',
+ 'Guaifenesin/Ammonium Chloride/Ammonium Carbonate',
+ 'Guaifenesin/Cetylpyridinium Chloride',
+ 'Guaifenesin/Levomenthol',
+ 'Guaifenesin/Pseudoephedrine',
+ 'Guaifenesin/Treacle/Glucose',
+ 'Guanethidine',
+ 'Guanethidine Monosulphate',
+ 'Gygel',
+ 'Gyno-Daktarin',
+ 'Gyno-Pevaryl',
+ 'Gynoxin',
+ 'Haemate P',
+ 'Haemorrhoid Relief Ointment',
+ 'Haldol',
+ 'Haldol Decanoate',
+ 'Halogenated Phenols/Phenol',
+ 'Haloperidol',
+ 'Haloperidol Decanoate',
+ 'Hamol Senna Tablets',
+ 'Happinose',
+ 'Harmogen',
+ 'Havrix',
+ 'Haymine Tablets',
+ 'Hbvaxpro',
+ 'Hedex',
+ 'Hedex Extra',
+ 'Hedex Ibuprofen',
+ 'Helixate Nexgen',
+ 'Hemabate',
+ 'Heminevrin Capsules',
+ 'Heparin Sodium',
+ 'Hepatyrix',
+ 'Hepsal',
+ 'Hepsera',
+ 'Herceptin',
+ 'Herpid',
+ 'Hexetidine',
+ 'Hexopal',
+ 'Hexylresorcinol',
+ 'Hexylresorcinol/Benzalkonium Chloride',
+ 'Hiprex',
+ 'Histac',
+ 'Histamine Dihydrochloride',
+ 'Honey/Glucose/Lemon',
+ 'Honey/Menthol',
+ 'Horizem',
+ 'Hormonin',
+ 'Humalog',
+ 'Humalog Mix',
+ 'Humatrope',
+ 'Humira',
+ 'Humulin I',
+ 'Humulin M3',
+ 'Humulin S',
+ 'Hyaluronidase',
+ 'Hycamtin',
+ 'Hydralazine',
+ 'Hydralazine Hydrochloride',
+ 'Hydrea',
+ 'Hydrochlorothiazide/Irbesartan',
+ 'Hydrochlorothiazide/Losartan',
+ 'Hydrochlorothiazide/Olmesartan',
+ 'Hydrochlorothiazide/Olmesartan Medoxomil',
+ 'Hydrochlorothiazide/Quinapril',
+ 'Hydrochlorothiazide/Telmisartan',
+ 'Hydrochlorothiazide/Valsartan',
+ 'Hydrocortisone',
+ 'Hydrocortisone Acetate',
+ 'Hydrocortisone Acetate/Lidocaine',
+ 'Hydrocortisone Acetate/Pramocaine Hydrochloride',
+ 'Hydrocortisone Acetate/Sodium Fusidate',
+ 'Hydrocortisone Butyrate',
+ 'Hydrocortisone Sodium Phosphate',
+ 'Hydrocortisone Sodium Succinate',
+ 'Hydrocortisone/Lactic Acid/Urea',
+ 'Hydrocortisone/Lidocaine',
+ 'Hydrocortisone/Miconazole Nitrate',
+ 'Hydrocortisone/Neomycin Sulphate/Polymyxin B Sulphate',
+ 'Hydrocortisone/Urea',
+ 'Hydrocortistab',
+ 'Hydroflumethiazide/Spironolactone',
+ 'Hydromol Bath And Shower Emollient',
+ 'Hydromol Cream',
+ 'Hydrotalcite Suspension',
+ 'Hydroxocobalamin',
+ 'Hydroxycarbamide',
+ 'Hydroxychloroquine',
+ 'Hydroxychloroquine Sulphate',
+ 'Hydroxyethyl Salicylate/Methyl Nicotinate',
+ 'Hydroxyethyl Salicylate/Methyl Nicotinate/Capsicum Oleoresin',
+ 'Hydroxyzine Hydrochloride',
+ 'Hyetellose',
+ 'Hygroton',
+ 'Hyoscine',
+ 'Hyoscine Butylbromide',
+ 'Hyoscine Hydrobromide',
+ 'Hypnomidate',
+ 'Hypnovel',
+ 'Hypolar',
+ 'Hypovase',
+ 'Hypromellose',
+ 'Hypromellose/Dextran 70',
+ 'Hypurin Bovine Isophane',
+ 'Hypurin Bovine Lente',
+ 'Hypurin Bovine Neutral',
+ 'Hypurin Bovine Protamine Zinc',
+ 'Hypurin Porcine',
+ 'Hypurin Porcine Isophane',
+ 'Hypurin Porcine Neutral',
+ 'Hytrin',
+ 'Ibandronic Sodium Monohydrate',
+ 'Ibufem',
+ 'Ibuprofen',
+ 'Ibuprofen/Codeine Phosphate',
+ 'Ibuprofen/Levomenthol',
+ 'Ibuprofen/Phenylephrine',
+ 'Ibuprofen/Pseudoephedrine',
+ 'Idarubicin Hydrochloride',
+ 'Idoxuridine',
+ 'Iglu Gel',
+ 'Ikorel',
+ 'Iloprost Trometamol',
+ 'Ilube',
+ 'Imatinib Mesilate',
+ 'Imigran',
+ 'Imigran Nasal Spray',
+ 'Imigran Recovery',
+ 'Imipramine Hydrochloride',
+ 'Imiquimod',
+ 'Immucyst',
+ 'Imodium',
+ 'Imodium Plus',
+ 'Implanon',
+ 'Imunovir',
+ 'Imuran',
+ 'Imuvac',
+ 'Incivo',
+ 'Increlex',
+ 'Indacaterol Maleate',
+ 'Indapamide',
+ 'Indapamide Hemihydrate',
+ 'Indapamide/Perindopril Arginine',
+ 'Inderal',
+ 'Indivina',
+ 'Indolar',
+ 'Indometacin',
+ 'Indomod',
+ 'Indoramin Hydrochloride',
+ 'Inegy',
+ 'Infacol',
+ 'Infanrix Ipv',
+ 'Infanrix-Ipv+Hib',
+ 'Infliximab',
+ 'Influenza Vaccine',
+ 'Influvac',
+ 'Innohep',
+ 'Innovace',
+ 'Innozide',
+ 'Inosine Pranobex',
+ 'Inositol Nicotinate',
+ 'Inovelon',
+ 'Inspra',
+ 'Insulatard',
+ 'Insulin Aspart',
+ 'Insulin Aspart/Insulin Aspart Protamine',
+ 'Insulin Detemir',
+ 'Insulin Glargine',
+ 'Insulin Glulisine',
+ 'Insulin Isophane Bovine',
+ 'Insulin Isophane Human',
+ 'Insulin Isophane Human/Insulin Soluble Human',
+ 'Insulin Isophane Porcine',
+ 'Insulin Isophane Porcine/Insulin Soluble Porcine',
+ 'Insulin Lispro',
+ 'Insulin Lispro/Insulin Lispro Protamine',
+ 'Insulin Protamine Zinc Bovine',
+ 'Insulin Soluble Bovine',
+ 'Insulin Soluble Human',
+ 'Insulin Soluble Porcine',
+ 'Insulin Zinc Suspension Mixed Bovine',
+ 'Insuman Basal',
+ 'Insuman Comb',
+ 'Insuman Rapid',
+ 'Intal',
+ 'Integrilin',
+ 'Intelence',
+ 'Interferon Alfa-2A (Rbe)',
+ 'Interferon Alfa-2B (Rbe)',
+ 'Interferon Beta-1A',
+ 'Interferon Beta-1B',
+ 'Introna',
+ 'Invanz',
+ 'Invega',
+ 'Invirase',
+ 'Iopidine',
+ 'Ipocol',
+ 'Ipratropium',
+ 'Ipratropium/Salbutamol',
+ 'Irbesartan',
+ 'Iressa',
+ 'Irinotecan Hydrochloride Trihydrate',
+ 'Iron Dextran',
+ 'Iron Sucrose',
+ 'Isentress',
+ 'Ismelin',
+ 'Isocarboxazid',
+ 'Isoflurane',
+ 'Isoket',
+ 'Isoniazid',
+ 'Isoniazid/Pyrazinamide/Rifampicin',
+ 'Isoniazid/Rifampicin',
+ 'Isopropyl Myristate/Liquid Paraffin',
+ 'Isopto Alkaline',
+ 'Isopto Plain',
+ 'Isosorbide Dinitrate',
+ 'Isosorbide Mononitrate',
+ 'Isotretinoin',
+ 'Isotrex',
+ 'Isotrexin',
+ 'Isovorin',
+ 'Ispaghula Husk Granules',
+ 'Ispaghula Husk/Mebeverine',
+ 'Isradipine',
+ 'Istin',
+ 'Itraconazole',
+ 'Ivabradine',
+ 'Ixiaro',
+ 'Janumet',
+ 'Januvia',
+ 'Javlor',
+ 'Junior Meltus Chesty Coughs With Catarrh',
+ 'Kaletra',
+ 'Kaolin And Morphine Mixture',
+ 'Kaolin/Calcium Carbonate',
+ 'Karvol Decongestant Capsules',
+ 'Karvol Decongestant Drops',
+ 'Kefadim',
+ 'Keflex',
+ 'Keftid',
+ 'Kemadrin',
+ 'Kemicetine',
+ 'Kenalog',
+ 'Kentera',
+ 'Kentipine',
+ 'Kenzem',
+ 'Keppra',
+ 'Keral',
+ 'Ketalar',
+ 'Ketamine Hydrochloride',
+ 'Ketek',
+ 'Ketocid',
+ 'Ketoconazole',
+ 'Ketoprofen',
+ 'Ketorolac Trometamol',
+ 'Ketotifen',
+ 'Ketovail',
+ 'Ketovite Liquid',
+ 'Kivexa',
+ 'Klaricid',
+ 'Kliofem',
+ 'Kliovance',
+ 'Kolanticon Gel',
+ 'Kytril',
+ 'Lacidipine',
+ 'Lacosamide',
+ 'Lacrilube',
+ 'Lactulose',
+ 'Lamictal',
+ 'Lamisil',
+ 'Lamivudine',
+ 'Lamivudine/Zidovudine',
+ 'Lamotrigine',
+ 'Lanacane Cream',
+ 'Lanolin/White Soft Paraffin/Liquid Paraffin',
+ 'Lanoxin',
+ 'Lanreotide Acetate',
+ 'Lansoprazole',
+ 'Lanthanum Carbonate',
+ 'Lantus',
+ 'Lanvis',
+ 'Lapatinib Ditosylate Monohydrate',
+ 'Larapam',
+ 'Largactil',
+ 'Lariam',
+ 'Laryng-O-Jet',
+ 'Lasilactone',
+ 'Lasix',
+ 'Lasoride',
+ 'Latanoprost',
+ 'Latanoprost/Timolol Maleate',
+ 'Lauromacrogol 400/Heparinoid',
+ 'Laxido',
+ 'Leflunomide',
+ 'Lemon/Honey/Levomenthol/Citric Acid',
+ 'Lemsip Cold And Flu',
+ 'Lemsip Cough Chesty',
+ 'Lemsip Dry Cough Liquid',
+ 'Lemsip Flu 12 Hour Ibuprofen And Pseudoephedrine',
+ 'Lemsip Max All Day Cold And Flu Tablets',
+ 'Lemsip Max All Night Cold And Flu Tablets',
+ 'Lemsip Max All-In-One',
+ 'Lemsip Max Cold And Flu Breathe Easy',
+ 'Lemsip Max Cold And Flu Direct',
+ 'Lemsip Max Day And Night Cold And Flu Relief Capsules',
+ 'Lemsip Max Flu',
+ 'Lemsip Max Sinus',
+ 'Lemsip Max Sinus Capsules',
+ 'Lenalidomide',
+ 'Lenograstim (Rch)',
+ 'Lercanidipine',
+ 'Lescol',
+ 'Letrozole',
+ 'Leukeran',
+ 'Leuprorelin Acetate',
+ 'Leustat',
+ 'Levemir',
+ 'Levetiracetam',
+ 'Levitra',
+ 'Levobunolol Hydrochloride',
+ 'Levobupivacaine Hydrochloride',
+ 'Levocetirizine Dihydrochloride',
+ 'Levofloxacin',
+ 'Levofloxacin Hemihydrate',
+ 'Levomenthol',
+ 'Levomenthol/Amylmetacresol/Dichlorobenzyl Alcohol',
+ 'Levomenthol/Squill/Liquorice',
+ 'Levomepromazine Maleate',
+ 'Levonelle',
+ 'Levonorgestrel',
+ 'Levothyroxine Sodium',
+ 'Lexpec Folic Acid',
+ 'Li-Liquid',
+ 'Librium',
+ 'Librofem',
+ 'Lidocaine',
+ 'Lidocaine Hydrochloride',
+ 'Lidocaine Hydrochloride/Adrenaline Acid Tartrate',
+ 'Lidocaine/Aminoacridine',
+ 'Lidocaine/Cetalkonium Chloride',
+ 'Lidocaine/Cetylpyridinium Chloride',
+ 'Lidocaine/Chlorhexidine',
+ 'Lidocaine/Chlorocresol/Cetylpyridinium Chloride',
+ 'Lidocaine/Methylprednisolone Acetate',
+ 'Lidocaine/Zinc Sulphate/Cetrimide',
+ 'Light Liquid Paraffin',
+ 'Linezolid',
+ 'Lioresal',
+ 'Liothyronine Sodium',
+ 'Lipantil',
+ 'Lipitor',
+ 'Lipobase',
+ 'Liposic',
+ 'Lipostat',
+ 'Liqufilm Tears',
+ 'Liquid Paraffin',
+ 'Liquid Paraffin/Acetylated Wool Alcohols',
+ 'Liquid Paraffin/Isopropyl Myristate',
+ 'Liquid Paraffin/White Soft Paraffin',
+ 'Liquid Paraffin/Wool Alcohols/White Soft Paraffin',
+ 'Liraglutide',
+ 'Lisinopril',
+ 'Lisinopril/Hydrochlorothiazide',
+ 'Liskonum',
+ 'Lisopress',
+ 'Litak',
+ 'Lithium Carbonate',
+ 'Lithium Citrate',
+ 'Livial',
+ 'Loceryl',
+ 'Locoid',
+ 'Lodine',
+ 'Lodoxamide Trometamol',
+ 'Loestrin',
+ 'Lofepramine',
+ 'Lofepramine Hydrochloride',
+ 'Logynon',
+ 'Lomont',
+ 'Lomustine',
+ 'Loniten',
+ 'Lopace',
+ 'Loperamide And Rehydration Powder',
+ 'Loperamide Hydrochloride',
+ 'Lopid',
+ 'Lopinavir/Ritonavir',
+ 'Loprazolam',
+ 'Lopresor',
+ 'Loratadine',
+ 'Lorazepam',
+ 'Loron 520',
+ 'Losartan',
+ 'Losec',
+ 'Losec Iv',
+ 'Losec Mups',
+ 'Lotemax',
+ 'Loteprednol Etabonate',
+ 'Luborant',
+ 'Lucentis',
+ 'Lumigan',
+ 'Lustral',
+ 'Lutropin Alfa',
+ 'Luveris',
+ 'Lyclear Cream Rinse',
+ 'Lyclear Dermal Cream',
+ 'Lyflex',
+ 'Lymecycline',
+ 'Lypsyl Cold Sore Gel',
+ 'Lyrica',
+ 'Lyrinel',
+ 'Lysine Acetylsalicylate/Metoclopramide Hydrochloride',
+ 'Lysodren',
+ 'Lysovir',
+ 'Maalox',
+ 'Maalox Plus',
+ 'Mabcampath',
+ 'Mabthera',
+ 'Mackenzies Smelling Salts',
+ 'Macrobid',
+ 'Macrodantin',
+ 'Macrogol 4000',
+ 'Macrogol Compound Powder Npf',
+ 'Macugen',
+ 'Madopar',
+ 'Magnapen Vial',
+ 'Magnesium Hydroxide Oral Suspension',
+ 'Magnesium Hydroxide Tablets',
+ 'Magnesium Hydroxide/Simeticone/Aluminium Hydroxide Gel Dried',
+ 'Magnesium Trisilicate/Sodium Bicarbonate/Alginic Acid/Aluminium Hydroxide',
+ 'Magnevist',
+ 'Malarone',
+ 'Malathion',
+ 'Mandafen',
+ 'Manerix',
+ 'Maraviroc',
+ 'Marcain Heavy',
+ 'Marcain Polyamp Steripack',
+ 'Marevan',
+ 'Marvelon',
+ 'Mastaflu',
+ 'Maxalt',
+ 'Maxidex',
+ 'Maxitrol Eye Drops',
+ 'Maxitrol Eye Ointment',
+ 'Maxolon',
+ 'Maxtrex',
+ 'Mebendazole',
+ 'Mebeverine Hydrochloride',
+ 'Mecasermin',
+ 'Meclozine',
+ 'Medicated Talc',
+ 'Medijel Gel',
+ 'Medijel Pastilles',
+ 'Medised For Children',
+ 'Medrone',
+ 'Medroxyprogesterone Acetate',
+ 'Mefenamic',
+ 'Mefenamic Acid',
+ 'Mefloquine Hydrochloride',
+ 'Megace',
+ 'Megestrol Acetate',
+ 'Meggezones',
+ 'Meglumine Gadobenate',
+ 'Meglumine Gadopentetate',
+ 'Melatonin',
+ 'Meloxicam',
+ 'Melphalan',
+ 'Melphalan Hydrochloride',
+ 'Memantine Hydrochloride',
+ 'Menadiol Sodium Phosphate',
+ 'Meningitec',
+ 'Menitorix',
+ 'Menjugate',
+ 'Menopur',
+ 'Menotrophin',
+ 'Menthol Pastilles',
+ 'Menthol/Anise Oil',
+ 'Menthol/Camphor/Pine Needle Oil',
+ 'Menthol/Eucalyptus',
+ 'Menthol/Peppermint',
+ 'Menthol/Pine Sylvestris Oil/Abietis Oil',
+ 'Mepradec',
+ 'Mepyramine',
+ 'Merbentyl',
+ 'Mercaptopurine',
+ 'Mercilon',
+ 'Merional',
+ 'Merocaine',
+ 'Merocets Lozenges',
+ 'Merocets Plus',
+ 'Meronem',
+ 'Meropenem Trihydrate',
+ 'Mesalazine',
+ 'Mesren',
+ 'Mesterolone',
+ 'Mestinon',
+ 'Mestranol/Norethisterone',
+ 'Metalyse',
+ 'Meted',
+ 'Meted Shampoo',
+ 'Metenix',
+ 'Metformin',
+ 'Metformin Hydrochloride',
+ 'Metformin Hydrochloride/Sitagliptin Phosphate',
+ 'Metformin Hydrochloride/Vildagliptin',
+ 'Metformin/Pioglitazone',
+ 'Methenamine Hippurate',
+ 'Methotrexate',
+ 'Methotrexate Sodium',
+ 'Methoxy Polyethylene Glycol-Epoetin Beta',
+ 'Methoxymethane/Hydroxyethyl Salicylate/Isopentane',
+ 'Methyl Aminolevulinate Hydrochloride',
+ 'Methyl Salicylate',
+ 'Methyl Salicylate/Menthol',
+ 'Methyl Salicylate/Menthol/Camphor',
+ 'Methyl Salicylate/Menthol/Capsicum/Camphor',
+ 'Methylcellulose',
+ 'Methylnaltrexone Bromide',
+ 'Methylphenidate Hydrochloride',
+ 'Methylprednisolone',
+ 'Methylprednisolone Acetate',
+ 'Methylprednisolone Sodium Succinate',
+ 'Methysergide Maleate',
+ 'Metipranolol',
+ 'Metoclopramide',
+ 'Metoclopramide Hydrochloride',
+ 'Metoclopramide Hydrochloride/Paracetamol',
+ 'Metolazone',
+ 'Metopirone',
+ 'Metoprolol',
+ 'Metrogel',
+ 'Metrolyl',
+ 'Metronidazole',
+ 'Metronidazole Benzoate',
+ 'Metrosa',
+ 'Metrotop',
+ 'Metvix',
+ 'Metyrapone',
+ 'Miacalcic',
+ 'Micafungin Sodium',
+ 'Micardis',
+ 'Micardisplus',
+ 'Miconazole',
+ 'Miconazole Nitrate',
+ 'Microgynon 30',
+ 'Micronor',
+ 'Micropirin',
+ 'Midazolam Hydrochloride',
+ 'Mifegyne',
+ 'Mifepristone',
+ 'Migard',
+ 'Migril',
+ 'Mildison Lipocream',
+ 'Milrinone',
+ 'Mimpara',
+ 'Mini-Plasco Lidocaine',
+ 'Minijet Adrenaline',
+ 'Minijet Amiodarone',
+ 'Minijet Atropine',
+ 'Minijet Furosemide',
+ 'Minijet Lidocaine',
+ 'Minijet Naloxone',
+ 'Minijet Sodium Bicarbonate',
+ 'Minims Artificial Tears',
+ 'Minims Atropine',
+ 'Minims Chloramphenicol',
+ 'Minims Cyclopentolate Hydrochloride',
+ 'Minims Dexamethasone',
+ 'Minims Lidocaine And Fluorescein',
+ 'Minims Metipranolol',
+ 'Minims Pilocarpine Nitrate',
+ 'Minims Prednisolone',
+ 'Minims Proxymetacaine And Fluorescein',
+ 'Minims Tetracaine Hydrochloride',
+ 'Minims Tropicamide',
+ 'Minocin',
+ 'Minocycline Hydrochloride',
+ 'Minodiab',
+ 'Minoxidil',
+ 'Miochol-E',
+ 'Mirapexin',
+ 'Mircera',
+ 'Mirena',
+ 'Mirtazapine',
+ 'Misoprostol',
+ 'Misoprostol/Naproxen',
+ 'Mitomycin',
+ 'Mitomycin-C Kyowa',
+ 'Mitotane',
+ 'Mitoxantrone Hydrochloride',
+ 'Mivacron',
+ 'Mivacurium Chloride',
+ 'Mixtard',
+ 'Mizolastine',
+ 'Mizollen',
+ 'Mmrvaxpro',
+ 'Mobiflex',
+ 'Moclobemide',
+ 'Modafinil',
+ 'Modalim',
+ 'Modecate',
+ 'Modrenal',
+ 'Moduret',
+ 'Moduretic',
+ 'Moexipril',
+ 'Moexipril Hydrochloride',
+ 'Mogadon',
+ 'Molaxole',
+ 'Molipaxin',
+ 'Mometasone',
+ 'Mometasone Furoate',
+ 'Monomil',
+ 'Monosorb',
+ 'Montelukast Sodium',
+ 'Morhulin Ointment',
+ 'Morphine',
+ 'Motens',
+ 'Motifene',
+ 'Motilium',
+ 'Motilium Tablet',
+ 'Movelat',
+ 'Movicol',
+ 'Moxifloxacin Hydrochloride',
+ 'Moxisylyte Hydrochloride',
+ 'Moxonidine',
+ 'Mucodyne',
+ 'Mucogel',
+ 'Multaq',
+ 'Multi-Action Actifed Tablets',
+ 'Multihance',
+ 'Multiparin',
+ 'Mupirocin Calcium',
+ 'Muse',
+ 'Mycamine',
+ 'Mycil Athletes Foot Spray',
+ 'Mycil Ointment',
+ 'Mycil Powder',
+ 'Mycobutin',
+ 'Mycophenolate Mofetil',
+ 'Mycophenolate Mofetil Hydrochloride',
+ 'Mycophenolate Sodium',
+ 'Mycota Spray',
+ 'Myfortic',
+ 'Myleran',
+ 'Myocet',
+ 'Myocrisin',
+ 'Myotonine',
+ 'Mysoline',
+ 'Nabilone',
+ 'Nabumetone',
+ 'Nadolol',
+ 'Nafarelin Acetate',
+ 'Naftidrofuryl',
+ 'Naftidrofuryl Oxalate',
+ 'Nalcrom',
+ 'Nalidixic Acid',
+ 'Naloxone Hydrochloride',
+ 'Nandrolone Decanoate',
+ 'Naphazoline',
+ 'Napratec',
+ 'Naprosyn',
+ 'Naproxen',
+ 'Naproxen/Esomeprazole Magnesium Trihydrate',
+ 'Naramig',
+ 'Naratriptan Hydrochloride',
+ 'Naropin',
+ 'Nasacort',
+ 'Naseptin',
+ 'Nasofan',
+ 'Nasonex',
+ 'Natalizumab',
+ 'Natecal D3',
+ 'Nateglinide',
+ 'Natracalm',
+ 'Natrasleep',
+ 'Natrilix',
+ 'Natrilix Sr',
+ 'Navelbine',
+ 'Nebilet',
+ 'Nebivolol',
+ 'Nebivolol Hydrochloride',
+ 'Nedocromil',
+ 'Nedocromil Sodium',
+ 'Nelarabine',
+ 'Neo-Cytamen',
+ 'Neo-Naclex',
+ 'Neoclarityn',
+ 'Neofel',
+ 'Neomercazole',
+ 'Neomycin Sulphate',
+ 'Neoral',
+ 'Neorecormon',
+ 'Neostigmine Metilsulfate',
+ 'Neostigmine Metilsulfate/Glycopyrronium Bromide',
+ 'Neotigason',
+ 'Neulactil',
+ 'Neulasta',
+ 'Neupogen',
+ 'Neupro',
+ 'Neurobloc',
+ 'Neurontin',
+ 'Neutrogena Norwegian Formula Cream',
+ 'Neutrogena T-Gel Shampoo',
+ 'Nevirapine',
+ 'Nevirapine Hemihydrate',
+ 'Nexavar',
+ 'Nexium',
+ 'Nexium Iv',
+ 'Niaspan',
+ 'Nicam Gel',
+ 'Nicardipine',
+ 'Nicardipine Hydrochloride',
+ 'Nicorandil',
+ 'Nicorette Gum',
+ 'Nicorette Inhalator',
+ 'Nicorette Invisi Patches',
+ 'Nicorette Microtab',
+ 'Nicorette Nasal Spray',
+ 'Nicorette Patches',
+ 'Nicotinamide',
+ 'Nicotine Gum',
+ 'Nicotine Inhaler',
+ 'Nicotine Lozenges',
+ 'Nicotine Nasal Spray',
+ 'Nicotine Patches',
+ 'Nicotine Sublingual Tablets',
+ 'Nicotinell Gum',
+ 'Nicotinell Lozenges',
+ 'Nicotinell Patches',
+ 'Nicotinic Acid',
+ 'Nifedipine',
+ 'Nifopress',
+ 'Night Nurse',
+ 'Nilotinib Hydrochloride Monohydrate',
+ 'Nimbex',
+ 'Nimodipine',
+ 'Nimotop',
+ 'Nipent',
+ 'Niquitin Gum',
+ 'Niquitin Lozenges',
+ 'Niquitin Mini Lozenges',
+ 'Niquitin Patches',
+ 'Nirolex Chesty Cough Linctus',
+ 'Nirolex Dry Cough Linctus',
+ 'Nirolex Dry Cough Relief Lozenges',
+ 'Nirolex Dry Coughs With Decongestant',
+ 'Nitrazepam',
+ 'Nitrofurantoin',
+ 'Nizatidine',
+ 'Nizoral',
+ 'Non-Drowsy Sudafed Childrens Syrup',
+ 'Nonacog Alfa',
+ 'Nonoxinol-9',
+ 'Nootropil',
+ 'Noradrenaline Acid Tartrate',
+ 'Norcuron',
+ 'Norditropin Nordiflex',
+ 'Norditropin Simplexx',
+ 'Norethisterone',
+ 'Norethisterone Enantate',
+ 'Norfloxacin',
+ 'Norgalax',
+ 'Norgeston',
+ 'Noriday',
+ 'Norimin',
+ 'Norimode',
+ 'Norinyl-1',
+ 'Noristerat',
+ 'Normacol',
+ 'Normacol Plus',
+ 'Normal Immunoglobulin Human',
+ 'Nortriptyline',
+ 'Nortriptyline Hydrochloride',
+ 'Norvir',
+ 'Norzol',
+ 'Novofem',
+ 'Novomix 30',
+ 'Novorapid',
+ 'Novoseven',
+ 'Noxafil',
+ 'Nozinan Tablets',
+ 'Nu-Seals',
+ 'Nupercainal',
+ 'Nurofen',
+ 'Nurofen Cold And Flu',
+ 'Nurofen Plus',
+ 'Nutrizym',
+ 'Nutropinaq',
+ 'Nuvaring',
+ 'Nyogel',
+ 'Nystan',
+ 'Nystatin',
+ 'Nytol',
+ 'Occlusal',
+ 'Octim',
+ 'Octocog Alfa',
+ 'Octreotide Acetate',
+ 'Ocufen',
+ 'Oculotect',
+ 'Ofatumumab',
+ 'Ofloxacin',
+ 'Ofloxacin Hydrochloride',
+ 'Oilatum Cream',
+ 'Oilatum Emollient',
+ 'Oilatum Gel',
+ 'Oilatum Junior Bath Additive',
+ 'Oilatum Junior Cream',
+ 'Oilatum Plus',
+ 'Oilatum Shower Gel Fragrance-Free',
+ 'Olanzapine',
+ 'Olanzapine Pamoate Monohydrate',
+ 'Olbetam',
+ 'Olmesartan',
+ 'Olmesartan Medoxomil',
+ 'Olmetec',
+ 'Olmetec Plus',
+ 'Olopatadine Hydrochloride',
+ 'Olsalazine Sodium',
+ 'Omalizumab',
+ 'Omeprazole',
+ 'Omeprazole Magnesium',
+ 'Omeprazole Sodium',
+ 'Oncotice',
+ 'Ondansetron',
+ 'Ondansetron Hydrochloride',
+ 'One-Alpha',
+ 'Onglyza',
+ 'Onkotrone',
+ 'Opatanol',
+ 'Opilon',
+ 'Opticrom',
+ 'Optilast',
+ 'Optimax',
+ 'Optivate',
+ 'Oraldene',
+ 'Orap',
+ 'Orelox',
+ 'Orencia',
+ 'Orgalutran',
+ 'Orgaran',
+ 'Original Andrews Salts',
+ 'Orlept',
+ 'Orlistat',
+ 'Orphenadrine Hydrochloride',
+ 'Ortho-Gynest',
+ 'Orudis',
+ 'Oruvail',
+ 'Otex',
+ 'Otomize',
+ 'Otosporin',
+ 'Otrivine Adult Measured Dose Sinusitis Spray',
+ 'Otrivine Adult Menthol Nasal Spray',
+ 'Otrivine Adult Nasal Drops',
+ 'Otrivine Adult Nasal Spray',
+ 'Otrivine Antistin',
+ 'Otrivine Child Nasal Drops',
+ 'Otrivine Mu-Cron',
+ 'Ovestin',
+ 'Ovitrelle',
+ 'Ovranette',
+ 'Ovysmen',
+ 'Oxactin',
+ 'Oxaliplatin',
+ 'Oxcarbazepine',
+ 'Oxerutins',
+ 'Oxis',
+ 'Oxprenolol',
+ 'Oxprenolol Hydrochloride',
+ 'Oxprenolol Hydrochloride/Cyclopenthiazide',
+ 'Oxybuprocaine Hydrochloride',
+ 'Oxybutynin',
+ 'Oxybutynin Hydrochloride',
+ 'Oxymetazoline',
+ 'Oxytetracycline Dihydrate',
+ 'Oxytocin',
+ 'Pabal',
+ 'Paclitaxel',
+ 'Paclitaxel Albumin',
+ 'Pain Relief Balm',
+ 'Paliperidone',
+ 'Paliperidone Palmitate',
+ 'Paludrine',
+ 'Panadol',
+ 'Panadol Extra Soluble Tablets',
+ 'Panadol Extra Tablets',
+ 'Panadol Night',
+ 'Pancrease',
+ 'Pancrex',
+ 'Pancuronium Bromide',
+ 'Pandemrix',
+ 'Panitumumab',
+ 'Panoxyl Acnegel',
+ 'Panoxyl Aquagel',
+ 'Panoxyl Cream',
+ 'Panoxyl Wash',
+ 'Pantoprazole',
+ 'Pantoprazole Sodium Sesquihydrate',
+ 'Paracetamol',
+ 'Paracetamol/Caffeine',
+ 'Paracetamol/Codeine /Caffeine',
+ 'Paracetamol/Codeine/Diphenhydramine/Caffeine',
+ 'Paracetamol/Codeine/Doxylamine/Caffeine',
+ 'Paracetamol/Dihydrocodeine',
+ 'Paracetamol/Diphenhydramine',
+ 'Paracetamol/Diphenhydramine Liquid',
+ 'Paracetamol/Diphenhydramine Tablets',
+ 'Paracetamol/Phenylephrine Sachets',
+ 'Paracetamol/Phenylephrine/Caffeine',
+ 'Paracetamol/Pseudoephedrine',
+ 'Paracetamol/Sodium Salicylate',
+ 'Paracetamol/Tramadol Hydrochloride',
+ 'Paramax',
+ 'Paramol Soluble Tablets',
+ 'Paramol Tablets',
+ 'Parathyroid Hormone',
+ 'Pardelprin',
+ 'Parecoxib Sodium',
+ 'Paricalcitol',
+ 'Pariet',
+ 'Parlodel',
+ 'Parmid',
+ 'Paroven',
+ 'Paroxetine',
+ 'Passion Flower',
+ 'Passion Flower/Valerian/Hops',
+ 'Passion Flower/Valerian/Hops/Scullcap/Jamaica Dogwood',
+ 'Pavacol-D',
+ 'Paxoran',
+ 'Pediacel',
+ 'Pegaptanib Sodium',
+ 'Pegasys',
+ 'Pegfilgrastim',
+ 'Peginterferon Alfa-2A',
+ 'Peginterferon Alfa-2B (Rbe)',
+ 'Pegvisomant',
+ 'Pemetrexed Disodium',
+ 'Penbritin',
+ 'Penciclovir',
+ 'Penicillamine',
+ 'Pennsaid',
+ 'Pentasa',
+ 'Pentostatin',
+ 'Pentoxifylline',
+ 'Pentrax Shampoo',
+ 'Pepcid',
+ 'Pepcidtwo',
+ 'Peppermint Oil Capsules',
+ 'Peppermint Oil/Capsicum/Elder Flower',
+ 'Peppermint Oil/Cinnamon/Clove Oil/Slippery Elm Bark',
+ 'Peppermint Oil/Menthol/Benzoin Compound/Ipecacuanha',
+ 'Peppermint Oil/Menthol/Myrrh',
+ 'Pepto-Bismol',
+ 'Perdix',
+ 'Perfalgan',
+ 'Pergolide Mesilate',
+ 'Pergoveris',
+ 'Periactin',
+ 'Pericyazine',
+ 'Perinal Spray',
+ 'Perindopril',
+ 'Perindopril Arginine',
+ 'Permethrin',
+ 'Persantin',
+ 'Pevaryl',
+ 'Pharmaton Capsules',
+ 'Pharmorubicin',
+ 'Phenelzine Sulphate',
+ 'Phenergan',
+ 'Phenol',
+ 'Phenol/Aromatic Ammonia/Strong Ammonia',
+ 'Phenol/Chlorhexidine Digluconate',
+ 'Phenol/Chlorhexidine Gluconate',
+ 'Phenylephrine Hydrochloride',
+ 'Phenylephrine Hydrochloride/Caffeine/Paracetamol',
+ 'Phenylephrine/Caffeine/Paracetamol Dual Relief',
+ 'Phenylephrine/Caffeine/Paracetamol Max Strength Capsules',
+ 'Phenylephrine/Guaifenesin/Paracetamol',
+ 'Phenylethyl Alcohol/Undecenoic Acid/Cetrimide',
+ 'Phenytoin',
+ 'Phenytoin Sodium',
+ 'Phillips Milk Of Magnesia',
+ 'Pholcodine',
+ 'Pholcodine Childrens Oral Solution',
+ 'Pholcodine Linctus',
+ 'Phosex',
+ 'Phyllocontin',
+ 'Physiotens',
+ 'Pilocarpine Hydrochloride',
+ 'Pilocarpine Nitrate',
+ 'Pimecrolimus',
+ 'Pimozide',
+ 'Pindolol',
+ 'Pinefeld',
+ 'Pioglitazone',
+ 'Piperacillin Sodium/Tazobactam Sodium',
+ 'Piportil',
+ 'Pipotiazine Palmitate',
+ 'Piracetam',
+ 'Piriteze Allergy Syrup',
+ 'Piriteze Allergy Tablets',
+ 'Piriton',
+ 'Piroxicam',
+ 'Pitressin',
+ 'Pivmecillinam Hydrochloride',
+ 'Pizotifen Hydrogen Malate',
+ 'Plaquenil',
+ 'Plavix',
+ 'Plendil',
+ 'Pletal',
+ 'Pneumovax Ii',
+ 'Podophyllotoxin',
+ 'Pollenshield',
+ 'Polysaccharide-Iron Complex',
+ 'Polytar Af Liquid',
+ 'Polytar Emollient',
+ 'Polytar Liquid',
+ 'Polytar Plus Liquid',
+ 'Polyvinyl Alcohol',
+ 'Ponstan',
+ 'Pork Actrapid',
+ 'Pork Mixtard',
+ 'Posaconazole',
+ 'Potassium Citrate',
+ 'Potassium Citrate/Citric Acid',
+ 'Potassium Clavulanate/Ticarcillin Sodium',
+ 'Povidone K25',
+ 'Povidone-Iodine Spray',
+ 'Powergel',
+ 'Pradaxa',
+ 'Pramipexole Dihydrochloride Monohydrate',
+ 'Prandin',
+ 'Prasugrel Hydrochloride',
+ 'Pravastatin Sodium',
+ 'Praxilene',
+ 'Prazosin',
+ 'Prazosin Hydrochloride',
+ 'Pred Forte',
+ 'Predfoam',
+ 'Prednisolone',
+ 'Prednisolone Acetate',
+ 'Prednisolone Sodium Metasulphobenzoate',
+ 'Prednisolone Sodium Phosphate',
+ 'Predsol',
+ 'Pregabalin',
+ 'Pregnyl',
+ 'Premarin',
+ 'Premique',
+ 'Prempak-C',
+ 'Preotact',
+ 'Prescal',
+ 'Preservex',
+ 'Prestim',
+ 'Prevenar',
+ 'Prezista',
+ 'Priadel Liquid',
+ 'Priadel Tablets',
+ 'Prilocaine Hydrochloride',
+ 'Prilocaine/Lidocaine',
+ 'Primacor',
+ 'Primaxin',
+ 'Primidone',
+ 'Primolut N',
+ 'Primovist',
+ 'Priorix',
+ 'Pro-Epanutin',
+ 'Pro-Viron',
+ 'Procarbazine Hydrochloride',
+ 'Prochlorperazine Maleate',
+ 'Prochlorperazine Mesilate',
+ 'Procoralan',
+ 'Proctofoam',
+ 'Proctosedyl',
+ 'Procyclidine Hydrochloride',
+ 'Progesterone',
+ 'Prograf',
+ 'Prograf Infusion',
+ 'Proguanil Hydrochloride',
+ 'Progynova Patches',
+ 'Progynova Tablets',
+ 'Prohance',
+ 'Proleukin',
+ 'Prolia',
+ 'Promethazine',
+ 'Promethazine Hydrochloride',
+ 'Promethazine Hydrochloride/Dextromethorphan Hydrobromide/Paracetamol',
+ 'Promethazine/Dextromethorphan/Paracetamol',
+ 'Promixin',
+ 'Propaderm',
+ 'Propafenone',
+ 'Propain Caplets',
+ 'Propain Plus',
+ 'Propantheline Bromide',
+ 'Propecia',
+ 'Propess',
+ 'Propine',
+ 'Propiverine Hydrochloride',
+ 'Propofol',
+ 'Propranolol',
+ 'Propranolol Hydrochloride',
+ 'Proscar',
+ 'Prostap',
+ 'Prostin E2',
+ 'Prosulf',
+ 'Protamine',
+ 'Protamine Sulphate',
+ 'Protelos',
+ 'Protirelin',
+ 'Protium',
+ 'Protopic',
+ 'Provera',
+ 'Provigil',
+ 'Proxymetacaine Hydrochloride',
+ 'Prozac',
+ 'Prucalopride Succinate',
+ 'Pseudoephedrine',
+ 'Pseudoephedrine Hydrochloride/Diphenhydramine Hydrochloride/Paracetamol',
+ 'Pseudoephedrine/Acrivastine',
+ 'Pseudoephedrine/Dextromethorphan',
+ 'Pseudoephedrine/Paracetamol/Diphenhydramine',
+ 'Pseudoephedrine/Pholcodine/Paracetamol',
+ 'Pseudoephedrine/Triprolidine',
+ 'Pseudoephedrine/Triprolidine/Dextromethorphan',
+ 'Pseudoephedrine/Triprolidine/Guaifenesin',
+ 'Psoriderm Bath Additive',
+ 'Psoriderm Cream',
+ 'Psoriderm Scalp Lotion',
+ 'Pulmicort',
+ 'Pulmozyme',
+ 'Pumo Bailly',
+ 'Puregon',
+ 'Puri-Nethol',
+ 'Pyralvex',
+ 'Pyridostigmine Bromide',
+ 'Pyrimethamine',
+ 'Questran',
+ 'Quetiapine Fumarate',
+ 'Quinapril',
+ 'Quinapril Hydrochloride',
+ 'Quinil',
+ 'Rabeprazole',
+ 'Rabipur',
+ 'Ralgex Cream',
+ 'Ralgex Freeze Spray',
+ 'Ralgex Heat Spray',
+ 'Raloxifene Hydrochloride',
+ 'Raltegravir',
+ 'Raltitrexed',
+ 'Ramipril',
+ 'Ranexa',
+ 'Ranibizumab',
+ 'Ranitic',
+ 'Ranitidine Hydrochloride',
+ 'Ranitil',
+ 'Ranolazine',
+ 'Rapamune',
+ 'Rapifen',
+ 'Rapilysin',
+ 'Rapitil',
+ 'Rasagiline Mesilate',
+ 'Rasilez',
+ 'Rebetol',
+ 'Rebif',
+ 'Reboxetine',
+ 'Reboxetine Mesilate',
+ 'Rectogesic',
+ 'Refolinon',
+ 'Regranex',
+ 'Regurin',
+ 'Relcofen',
+ 'Relenza',
+ 'Relestat',
+ 'Relifex',
+ 'Relistor',
+ 'Relpax',
+ 'Remedeine',
+ 'Remegel',
+ 'Remegel Wind Relief',
+ 'Remicade',
+ 'Remifentanyl Hydrochloride',
+ 'Reminyl',
+ 'Renagel',
+ 'Renvela',
+ 'Reopro',
+ 'Repaglinide',
+ 'Repevax',
+ 'Replenine-Vf',
+ 'Requip',
+ 'Resolor',
+ 'Resolve',
+ 'Resolve Extra',
+ 'Respontin',
+ 'Restandol',
+ 'Retalzem',
+ 'Retapamulin',
+ 'Reteplase',
+ 'Retigabine',
+ 'Retin-A',
+ 'Retrovir',
+ 'Revatio',
+ 'Revaxis',
+ 'Revlimid',
+ 'Rexocaine',
+ 'Reyataz',
+ 'Rhinocort',
+ 'Rhinolast',
+ 'Rhophylac',
+ 'Rhumalgan',
+ 'Riamet',
+ 'Rifabutin',
+ 'Rifadin',
+ 'Rifampicin',
+ 'Rifater',
+ 'Rifinah',
+ 'Rilutek',
+ 'Riluzole',
+ 'Rimactane',
+ 'Rimexolone',
+ 'Rinatec',
+ 'Rinstead Sugar Free Pastilles',
+ 'Risedronate Sodium',
+ 'Risedronate Sodium/Colecalciferol/Calcium Carbonate',
+ 'Risperdal',
+ 'Risperidone',
+ 'Ritalin',
+ 'Ritonavir',
+ 'Rituximab',
+ 'Rivaroxaban',
+ 'Rivastigmine',
+ 'Rivastigmine Hydrogen Tartrate',
+ 'Rivotril',
+ 'Rizatriptan Benzoate',
+ 'Roaccutane',
+ 'Roactemra',
+ 'Robinul',
+ 'Robinul-Neostigmine',
+ 'Robitussin Chesty Cough',
+ 'Robitussin Chesty Cough With Congestion',
+ 'Robitussin Dry Cough Medicine',
+ 'Rocaltrol',
+ 'Rocephin',
+ 'Rocuronium Bromide',
+ 'Roferon-A',
+ 'Roflumilast',
+ 'Ropinirole Hydrochloride',
+ 'Ropivacaine Hydrochloride',
+ 'Rosiced',
+ 'Rosuvastatin',
+ 'Rotarix',
+ 'Rotigotine',
+ 'Rozex',
+ 'Rufinamide',
+ 'Rupafin',
+ 'Rupatadine Fumarate',
+ 'Rynacrom',
+ 'Rythmodan',
+ 'Rythmodan Capsules',
+ 'Sabril',
+ 'Saflutan',
+ 'Saizen',
+ 'Salactol Paint',
+ 'Salagen',
+ 'Salatac Gel',
+ 'Salazopyrin',
+ 'Salbutamol',
+ 'Salicylic Acid',
+ 'Salicylic Acid Ointment',
+ 'Salicylic Acid Paint',
+ 'Salicylic Acid/Camphor',
+ 'Salicylic Acid/Coal Tar/Sulphur',
+ 'Salicylic Acid/Dithranol',
+ 'Salicylic Acid/Lactic Acid',
+ 'Salicylic Acid/Menthol/Ammonium Salicylate/Camphor',
+ 'Salicylic Acid/Mucopolysaccharide Polysulphate',
+ 'Salicylic Acid/Rhubarb Extract',
+ 'Salmeterol',
+ 'Salofalk',
+ 'Sandimmun',
+ 'Sandocal',
+ 'Sandocal+D',
+ 'Sandoglobulin Nf',
+ 'Sandostatin',
+ 'Sandrena',
+ 'Sanomigran',
+ 'Saquinavir Mesilate',
+ 'Savlon Antiseptic Cream',
+ 'Savlon Antiseptic Liquid',
+ 'Savlon Antiseptic Wound Wash',
+ 'Savlon Bites And Stings Pain Relief Gel',
+ 'Savlon Dry Spray',
+ 'Scheriproct',
+ 'Scholl Athletes Foot Cream',
+ 'Scholl Athletes Foot Powder',
+ 'Scholl Callous Removal Pads',
+ 'Scholl Corn And Callus Removal Liquid',
+ 'Scholl Corn Removal Plasters (Fabric)',
+ 'Scholl Corn Removal Plasters (Washproof)',
+ 'Scholl Polymer Gel Corn Removal Pads',
+ 'Scholl Seal And Heal Verruca Removal Gel',
+ 'Scopoderm Tts',
+ 'Sea-Legs Tablets',
+ 'Sebivo',
+ 'Sebomin',
+ 'Sebren',
+ 'Sectral',
+ 'Securon',
+ 'Selectajet Dopamine',
+ 'Selegiline Hydrochloride',
+ 'Selenium Sulphide',
+ 'Selexid',
+ 'Senna Fruit/Ispaghula',
+ 'Senna Syrup',
+ 'Senna Tablets',
+ 'Senna/Buckthorn Bark/Psyllium Seeds',
+ 'Senokot Comfort Tablets',
+ 'Senokot Dual Relief Tablets',
+ 'Senokot Hi-Fibre',
+ 'Senokot Max Strength',
+ 'Senokot Syrup',
+ 'Senokot Tablets',
+ 'Septrin',
+ 'Seractil',
+ 'Serc',
+ 'Serenace',
+ 'Seretide',
+ 'Serevent',
+ 'Seroquel',
+ 'Seroxat',
+ 'Sertraline',
+ 'Sertraline Hydrochloride',
+ 'Sevelamer Carbonate',
+ 'Sevelamer Hydrochloride',
+ 'Sevoflurane',
+ 'Sildenafil Citrate',
+ 'Silkis',
+ 'Silver Nitrate',
+ 'Simeticone',
+ 'Simeticone Drops',
+ 'Simeticone/Loperamide Hydrochloride',
+ 'Simponi',
+ 'Simulect',
+ 'Simvador',
+ 'Simvastatin',
+ 'Sinemet',
+ 'Singulair',
+ 'Sinthrome',
+ 'Sinutab Non-Drowsy',
+ 'Sirolimus',
+ 'Sitaxentan Sodium',
+ 'Skelid',
+ 'Skinoren',
+ 'Sleepeaze Tablets',
+ 'Slozem',
+ 'Sno Tears',
+ 'Snufflebabe Vapour Rub',
+ 'Sodiofolin',
+ 'Sodium Alginate/Calcium Carbonate/Sodium Bicarbonate',
+ 'Sodium Alginate/Magnesium Alginate',
+ 'Sodium Alginate/Potassium Bicarbonate',
+ 'Sodium Aurothiomalate',
+ 'Sodium Bicarbonate',
+ 'Sodium Bicarbonate/Citric Acid/Magnesium Sulphate',
+ 'Sodium Bicarbonate/Dill Seed Oil',
+ 'Sodium Citrate',
+ 'Sodium Citrate Compound',
+ 'Sodium Clodronate',
+ 'Sodium Cromoglicate',
+ 'Sodium Feredetate',
+ 'Sodium Fluoride',
+ 'Sodium Fluoride/Triclosan',
+ 'Sodium Fusidate',
+ 'Sodium Lauryl Ether Sulpho-Succinate/Sodium Lauryl Ether Sulphate',
+ 'Sodium Oxybate',
+ 'Sodium Picosulfate',
+ 'Sodium Pidolate',
+ 'Sodium Tetradecyl Sulfate',
+ 'Sodium Valproate',
+ 'Sofradex Ear/Eye Drops',
+ 'Solaraze',
+ 'Solian',
+ 'Solifenacin Succinate',
+ 'Solpadeine Headache Soluble Tablets',
+ 'Solpadeine Headache Tablets',
+ 'Solpadeine Migraine Ibuprofen And Codeine Tablets',
+ 'Solu-Cortef',
+ 'Solu-Medrone',
+ 'Somatropin',
+ 'Somatuline',
+ 'Somavert',
+ 'Sominex Herbal Tablets',
+ 'Somnite',
+ 'Sonata',
+ 'Sonovue',
+ 'Sorafenib Tosylate',
+ 'Sotacor',
+ 'Sotalol',
+ 'Sotalol Hydrochloride',
+ 'Spasmonal',
+ 'Spasmonal Forte',
+ 'Spiriva',
+ 'Spironolactone',
+ 'Sporanox',
+ 'Sprycel',
+ 'Squill/Capsicum',
+ 'Squill/Pumilio Pine Oil/Ipecacuanha/Liquorice',
+ 'St. Johns Wort',
+ 'Stalevo',
+ 'Stannous Fluoride',
+ 'Staril',
+ 'Starlix',
+ 'Statin',
+ 'Stavudine',
+ 'Stelara',
+ 'Stelazine',
+ 'Stemetil',
+ 'Stemetil Tablet',
+ 'Sterculia',
+ 'Sterculia/Frangula',
+ 'Stesolid',
+ 'Stiemycin',
+ 'Stilnoct',
+ 'Strattera',
+ 'Strepsils',
+ 'Strepsils Extra Lozenges',
+ 'Strepsils Orange With Vitamin C',
+ 'Strepsils Sore Throat And Blocked Nose Lozenges',
+ 'Streptase',
+ 'Streptokinase',
+ 'Stressless Tablets',
+ 'Stronazon',
+ 'Strontium Ranelate',
+ 'Stugeron 15',
+ 'Sucralfate',
+ 'Sucrose/Guaifenesin/Cetylpyridinium Chloride/Honey',
+ 'Sudafed',
+ 'Sudocrem',
+ 'Sugammadex Sodium',
+ 'Sulfadiazine',
+ 'Sulfamethoxazole/Trimethoprim',
+ 'Sulfasalazine',
+ 'Sulphur Hexafluoride',
+ 'Sulphur/Salicylic Acid',
+ 'Sulpiride',
+ 'Sulpor',
+ 'Sumatriptan',
+ 'Sumatriptan Succinate',
+ 'Sunitinib Malate',
+ 'Supralip',
+ 'Suprax',
+ 'Suprecur',
+ 'Suprefact',
+ 'Sure-Amp Lidocaine',
+ 'Surgam',
+ 'Surmontil',
+ 'Sustiva',
+ 'Sutent',
+ 'Suxamethonium Chloride',
+ 'Symbicort',
+ 'Symmetrel',
+ 'Synarel',
+ 'Syner-Kinase',
+ 'Synflorix',
+ 'Synphase',
+ 'Syntocinon',
+ 'Syntometrine',
+ 'Syprol',
+ 'Sytron',
+ 'Tabphyn',
+ 'Tacrolimus',
+ 'Tadalafil',
+ 'Tafluprost',
+ 'Tagamet',
+ 'Tambocor',
+ 'Tamiflu',
+ 'Tamoxifen Citrate',
+ 'Tamsulosin Hydrochloride',
+ 'Tarceva',
+ 'Targocid',
+ 'Targretin',
+ 'Tarivid',
+ 'Tarivid Injection',
+ 'Tarka',
+ 'Tasigna',
+ 'Tavanic',
+ 'Tavegil',
+ 'Taxol',
+ 'Taxotere',
+ 'Tazarotene',
+ 'Tazocin',
+ 'Tcp Antiseptic Cream',
+ 'Tcp Antiseptic Liquid',
+ 'Tcp Antiseptic Ointment',
+ 'Tcp Sore Throat Lozenges',
+ 'Tears Naturale',
+ 'Tegafur/Uracil',
+ 'Tegretol',
+ 'Teicoplanin',
+ 'Telbivudine',
+ 'Telfast',
+ 'Telithromycin',
+ 'Telmisartan',
+ 'Telzir',
+ 'Temazepam',
+ 'Temodal',
+ 'Temozolomide',
+ 'Temsirolimus',
+ 'Tenecteplase',
+ 'Tenif',
+ 'Tenofovir Disoproxil Fumarate',
+ 'Tenoret',
+ 'Tenoretic',
+ 'Tenormin',
+ 'Tenoxicam',
+ 'Tensipine',
+ 'Terazosin',
+ 'Terazosin Hydrochloride',
+ 'Terbinafine Hydrochloride',
+ 'Terbutaline',
+ 'Teriparatide',
+ 'Tetanus Immunoglobulin Human',
+ 'Tetracaine Hydrochloride',
+ 'Tetracycline Hydrochloride',
+ 'Tetralysal',
+ 'Teveten',
+ 'Thelin',
+ 'Theophylline',
+ 'Thiopental Sodium',
+ 'Throaties Strong Original Pastilles',
+ 'Thurfyl Salicylate/Hexyl Nicotinate/Ethyl Nicotinate',
+ 'Thwart',
+ 'Thymoglobuline',
+ 'Thyrogen',
+ 'Thyrotropin Alfa',
+ 'Tiagabine Hydrochloride Monohydrate',
+ 'Tiaprofenic Acid',
+ 'Tibolone',
+ 'Ticagrelor',
+ 'Ticovac',
+ 'Tigecycline',
+ 'Tiger Balm Red',
+ 'Tiger Balm White',
+ 'Tilade',
+ 'Tildiem',
+ 'Tiloket',
+ 'Tiloryth',
+ 'Tiludronate Disodium',
+ 'Timentin',
+ 'Timodine',
+ 'Timolol',
+ 'Timolol Maleate',
+ 'Timolol Maleate/Travoprost',
+ 'Timoptol',
+ 'Tinidazole',
+ 'Tinzaparin',
+ 'Tioconazole',
+ 'Tioguanine',
+ 'Tiotropium',
+ 'Tipranavir',
+ 'Tirofiban',
+ 'Tirofiban Hydrochloride',
+ 'Tixylix Baby Syrup',
+ 'Tixylix Chesty Cough',
+ 'Tixylix Dry Cough',
+ 'Tixylix Honey, Lemon And Glycerol Syrup',
+ 'Tixylix Toddler Syrup',
+ 'Tizanidine',
+ 'Tobi',
+ 'Tobradex',
+ 'Tobramycin',
+ 'Tocilizumab',
+ 'Toctino',
+ 'Tolcapone',
+ 'Tolfenamic Acid',
+ 'Tolnaftate',
+ 'Tolnaftate/Benzalkonium Chloride',
+ 'Tolnaftate/Chlorhexidine',
+ 'Tolterodine Tartrate',
+ 'Tomudex',
+ 'Topal Chewable Tablets',
+ 'Topamax',
+ 'Topiramate',
+ 'Topotecan Hydrochloride',
+ 'Torasemide',
+ 'Torem',
+ 'Toremifene Citrate',
+ 'Torisel',
+ 'Toviaz',
+ 'Tracleer',
+ 'Tracrium',
+ 'Tractocile',
+ 'Tramacet',
+ 'Tramadol Hydrochloride',
+ 'Trandolapril',
+ 'Trandolapril/Verapamil',
+ 'Trandolapril/Verapamil Hydrochloride',
+ 'Tranexamic Acid',
+ 'Transiderm-Nitro',
+ 'Trasicor',
+ 'Trasidrex',
+ 'Trastuzumab',
+ 'Travatan',
+ 'Travoprost',
+ 'Traxam',
+ 'Trazodone',
+ 'Trazodone Hydrochloride',
+ 'Trental',
+ 'Treosulfan',
+ 'Tretinoin',
+ 'Triadene',
+ 'Triamcinolone',
+ 'Triamcinolone Acetonide',
+ 'Triamterene/Hydrochlorothiazide',
+ 'Triapin',
+ 'Tridestra',
+ 'Trifluoperazine Hydrochloride',
+ 'Trihexyphenidyl Hydrochloride',
+ 'Trileptal',
+ 'Trilostane',
+ 'Trimethoprim',
+ 'Trimipramine',
+ 'Trimipramine Maleate',
+ 'Trimovate',
+ 'Trinordiol',
+ 'Trinovum',
+ 'Tripotassium Dicitratobismuthate',
+ 'Triptorelin Acetate',
+ 'Trisenox',
+ 'Trisequens',
+ 'Tritace',
+ 'Trizivir',
+ 'Trobalt',
+ 'Tropicamide',
+ 'Trospium Chloride',
+ 'Trosyl',
+ 'Trusopt',
+ 'Truvada',
+ 'Tryptophan',
+ 'Tums Antacid Tablets',
+ 'Turpentine Oil/Acetic Acid',
+ 'Turpentine/Dilute Ammonia/Acetic Acid',
+ 'Twinrix',
+ 'Tygacil',
+ 'Tylex',
+ 'Typherix',
+ 'Typhim Vi',
+ 'Typhoid Vaccine',
+ 'Tyrozets',
+ 'Tysabri',
+ 'Tyverb',
+ 'Ubretid',
+ 'Uftoral',
+ 'Ulipristal Acetate',
+ 'Ultiva',
+ 'Ultra Chloraseptic Anaesthetic Throat Spray',
+ 'Ultrabase',
+ 'Ultramol Soluble Tablets',
+ 'Undecenoic Acid/Dichlorophen',
+ 'Unguentum M',
+ 'Uniroid Hc',
+ 'Univer',
+ 'Urdox',
+ 'Urea',
+ 'Urea Hydrogen Peroxide',
+ 'Urea/Lauromacrogols',
+ 'Urispas',
+ 'Urokinase',
+ 'Ursodeoxycholic Acid',
+ 'Ursofalk',
+ 'Ursogal',
+ 'Ustekinumab',
+ 'Utinor',
+ 'Utovlan',
+ 'Vagifem',
+ 'Valaciclovir Hydrochloride',
+ 'Valcyte',
+ 'Valdoxan',
+ 'Valerian/Hops',
+ 'Valganciclovir Hydrochloride',
+ 'Vallergan',
+ 'Valoid',
+ 'Valproate Semisodium',
+ 'Valsartan',
+ 'Valtrex',
+ 'Vancomycin Hydrochloride',
+ 'Vaniqa',
+ 'Vaqta',
+ 'Vardenafil Hydrochloride Trihydrate',
+ 'Varenicline Tartrate',
+ 'Varicella-Zoster Vaccine',
+ 'Varilrix',
+ 'Vascace',
+ 'Vascalpha',
+ 'Vasogen',
+ 'Vectavir',
+ 'Vectibix',
+ 'Vecuronium Bromide',
+ 'Velcade',
+ 'Velosulin',
+ 'Vemurafenib',
+ 'Venlafaxine',
+ 'Venlafaxine Hydrochloride',
+ 'Venofer',
+ 'Ventavis',
+ 'Ventolin',
+ 'Vepesid',
+ 'Vera-Til',
+ 'Verapamil',
+ 'Verapamil Hydrochloride',
+ 'Vermox',
+ 'Versatis',
+ 'Vertab',
+ 'Verteporfin',
+ 'Vervain/Valerian/Scullcap/Hops',
+ 'Vervain/Valerian/Scullcap/Hops/Lupulus',
+ 'Vesanoid',
+ 'Vesicare',
+ 'Vexol',
+ 'Vfend',
+ 'Viagra',
+ 'Viatim',
+ 'Viazem',
+ 'Vibramycin',
+ 'Vibramycin-D',
+ 'Vibrio Cholerae',
+ 'Vicks Cough Syrup For Chesty Coughs',
+ 'Vicks Inhaler',
+ 'Vicks Medinite Syrup',
+ 'Vicks Sinex Decongestant Nasal Spray',
+ 'Vicks Sinex Micromist',
+ 'Vicks Sinex Soother',
+ 'Vicks Vaporub',
+ 'Victoza',
+ 'Victrelis',
+ 'Videx',
+ 'Vigabatrin',
+ 'Vigam',
+ 'Vildagliptin',
+ 'Vimovo',
+ 'Vimpat',
+ 'Vinblastine',
+ 'Vinblastine Sulphate',
+ 'Vincristine Sulphate',
+ 'Vinflunine Ditartrate',
+ 'Vinorelbine Tartrate',
+ 'Viraferon',
+ 'Viraferonpeg',
+ 'Viramune',
+ 'Viramune Suspension',
+ 'Viread',
+ 'Viridal',
+ 'Viscotears',
+ 'Viskaldix',
+ 'Visken',
+ 'Vistide',
+ 'Visudyne',
+ 'Vivotif',
+ 'Volibris',
+ 'Voltarol',
+ 'Voltarol Dispersible',
+ 'Voltarol Emulgel',
+ 'Voltarol Gel Patch',
+ 'Voltarol Rapid',
+ 'Voriconazole',
+ 'Votrient',
+ 'Warfarin',
+ 'Warticon',
+ 'Wasp-Eze Spray',
+ 'Waxsol',
+ 'White Soft Paraffin',
+ 'White Soft Paraffin/Light Liquid Paraffin',
+ 'White Soft Paraffin/Liquid Paraffin',
+ 'Wind-Eze Gel Caps',
+ 'Wind-Eze Tablets',
+ 'Windsetlers',
+ 'Witch Doctor Gel',
+ 'Witch Hazel Gel',
+ 'Woodwards Gripe Water',
+ 'Xalacom',
+ 'Xalatan',
+ 'Xamiol',
+ 'Xanax',
+ 'Xarelto',
+ 'Xatral',
+ 'Xeloda',
+ 'Xenical',
+ 'Xeplion',
+ 'Xigris',
+ 'Xipamide',
+ 'Xismox',
+ 'Xolair',
+ 'Xylocaine With Adrenaline',
+ 'Xylometazoline',
+ 'Xyloproct',
+ 'Xyrem',
+ 'Xyzal',
+ 'Yasmin',
+ 'Yeast Plasmolysate',
+ 'Yentreve',
+ 'Zacin',
+ 'Zaditen',
+ 'Zafirlukast',
+ 'Zaleplon',
+ 'Zamadol',
+ 'Zanaflex',
+ 'Zanamivir',
+ 'Zanidip',
+ 'Zantac',
+ 'Zaponex',
+ 'Zarontin',
+ 'Zavedos',
+ 'Zeasorb',
+ 'Zebinix',
+ 'Zeffix',
+ 'Zelboraf',
+ 'Zemplar',
+ 'Zemtard',
+ 'Zerit',
+ 'Zestoretic',
+ 'Zestril',
+ 'Ziagen',
+ 'Zibor',
+ 'Zidoval',
+ 'Zidovudine',
+ 'Zimovane',
+ 'Zinacef',
+ 'Zinc Oxide/Bismuth Subgallate/Peru Balsam/Bismuth Oxide',
+ 'Zinc Oxide/Cod Liver Oil',
+ 'Zinc Oxide/Lidocaine',
+ 'Zinc Oxide/Lidocaine/Benzoic Acid/Cinnamic Acid/Bismuth Oxide',
+ 'Zinc Oxide/Peru Balsam/Bismuth Oxide',
+ 'Zinc Paste/Calamine',
+ 'Zinc Undecenoate/Undecenoic Acid',
+ 'Zineryt',
+ 'Zinnat',
+ 'Zirtek Allergy Liquid',
+ 'Zirtek Allergy Tablets',
+ 'Zispin Soltab',
+ 'Zithromax',
+ 'Zocor',
+ 'Zofran',
+ 'Zofran Melt',
+ 'Zofran Suppository',
+ 'Zoladex',
+ 'Zoledronic Acid Monohydrate',
+ 'Zolmitriptan',
+ 'Zolpidem',
+ 'Zolpidem Tartrate',
+ 'Zolvera',
+ 'Zomacton',
+ 'Zomig',
+ 'Zonegran',
+ 'Zonisamide',
+ 'Zopiclone',
+ 'Zorac',
+ 'Zoton',
+ 'Zovirax',
+ 'Zovirax I.V.',
+ 'Zuclopenthixol Acetate',
+ 'Zuclopenthixol Decanoate',
+ 'Zuclopenthixol Dihydrochloride',
+ 'Zumenon',
+ 'Zyban',
+ 'Zyloric',
+ 'Zyomet',
+ 'Zypadhera',
+ 'Zyprexa',
+ 'Zyvox'
+ ]
+
diff --git a/src/openmolar/schema_upgrades/schema2_9to3_0.py b/src/openmolar/schema_upgrades/schema2_9to3_0.py
new file mode 100644
index 0000000..0f67b66
--- /dev/null
+++ b/src/openmolar/schema_upgrades/schema2_9to3_0.py
@@ -0,0 +1,149 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+'''
+This module provides a function 'run' which will move data
+to schema 3_0
+'''
+from __future__ import division
+
+import datetime
+import logging
+import os
+import sys
+
+from openmolar.settings import localsettings
+from openmolar.schema_upgrades.database_updater_thread import DatabaseUpdaterThread
+
+LOGGER = logging.getLogger("openmolar")
+
+SQLSTRINGS = [
+ 'drop table if exists standard_letters',
+ 'alter table newdocsprinted change column docname docname varchar(64)''',
+ '''
+create table standard_letters (
+ ix int(11) unsigned not null auto_increment ,
+ description char(64) UNIQUE NOT NULL,
+ body_text text NOT NULL,
+ footer text,
+PRIMARY KEY (ix)
+)''',
+]
+
+
+INSERT_QUERY = '''
+INSERT INTO standard_letters (description, body_text, footer)
+VALUES (%s, %s, %s)
+'''
+
+
+BODY = '''<br />
+<div align="center"><b>XRAY REQUEST</b></div>
+<br />
+<p>You have requested copies of your xrays to take with you to another practice.<br />
+Please be advise that we are happy to do this, and provide these as Jpeg files on CD-rom.
+</p>
+<p>
+There is, however, a nominal charge of £15.00 for this service, which is in line with British Dental Association recommendations.
+</p>
+<p>
+Should you wish to proceed, please complete the slip below and return it to us along with your remittance.
+On receipt of the slip, your xrays will normally be forwarded with 7 working days.
+</p>'''
+
+FOOTER = '''
+<br />
+<hr />
+<br />
+<p>
+I hereby request copies of my radiographs be sent to:<br />
+(delete as appropriate)
+<ul>
+<li>
+My home address (as above)
+</li>
+<li>
+Another dental practice (please give details overleaf).
+</li>
+</ul>
+</p>
+<p>
+I enclose a cheque for £ 15.00
+</p>
+<pre>
+Signed ________________________________________________
+
+Date ________________________________________________
+
+{{NAME}}
+(adp number {{SERIALNO}}))
+</pre>
+'''
+
+CLEANUPSTRINGS = [
+]
+
+
+class DatabaseUpdater(DatabaseUpdaterThread):
+
+ def transfer_data(self):
+ '''
+ function specific to this update.
+ '''
+ self.cursor.execute(INSERT_QUERY,
+ (_("XRay Request Letter"), BODY, FOOTER))
+
+ def run(self):
+ LOGGER.info("running script to convert from schema 2.9 to 3.0")
+ try:
+ self.connect()
+ #- execute the SQL commands
+ self.progressSig(50, _("creating new tables"))
+ self.execute_statements(SQLSTRINGS)
+
+ self.transfer_data()
+
+ self.progressSig(75, _("executing cleanup statements"))
+ self.execute_statements(CLEANUPSTRINGS)
+
+ self.progressSig(97, _('updating settings'))
+ LOGGER.info("updating stored database version in settings table")
+
+ self.update_schema_version(("3.0",), "2_9 to 3_0 script")
+
+ self.progressSig(100, _("updating stored schema version"))
+ self.commit()
+ self.completeSig(_("Successfully moved db to") + " 3.0")
+ return True
+ except Exception as exc:
+ LOGGER.exception("error transfering data")
+ self.rollback()
+ raise self.UpdateError(exc)
+
+if __name__ == "__main__":
+ dbu = DatabaseUpdater()
+ if dbu.run():
+ LOGGER.info("ALL DONE, conversion successful")
+ else:
+ LOGGER.warning("conversion failed")
diff --git a/src/openmolar/schema_upgrades/schema3_0to3_1.py b/src/openmolar/schema_upgrades/schema3_0to3_1.py
new file mode 100644
index 0000000..afea5ed
--- /dev/null
+++ b/src/openmolar/schema_upgrades/schema3_0to3_1.py
@@ -0,0 +1,289 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ############################################################################ #
+# # # #
+# # Copyright (c) 2009-2014 Neil Wallace <neil at openmolar.com> # #
+# # # #
+# # This file is part of OpenMolar. # #
+# # # #
+# # OpenMolar is free software: you can redistribute it and/or modify # #
+# # it under the terms of the GNU General Public License as published by # #
+# # the Free Software Foundation, either version 3 of the License, or # #
+# # (at your option) any later version. # #
+# # # #
+# # OpenMolar is distributed in the hope that it will be useful, # #
+# # but WITHOUT ANY WARRANTY; without even the implied warranty of # #
+# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # #
+# # GNU General Public License for more details. # #
+# # # #
+# # You should have received a copy of the GNU General Public License # #
+# # along with OpenMolar. If not, see <http://www.gnu.org/licenses/>. # #
+# # # #
+# ############################################################################ #
+
+'''
+This module provides a function 'run' which will move data
+to schema 3_1
+'''
+from __future__ import division
+
+from collections import namedtuple
+try:
+ from collections import OrderedDict
+except ImportError:
+ # OrderedDict only came in python 2.7
+ LOGGER.warning("using openmolar.backports for OrderedDict")
+ from openmolar.backports import OrderedDict
+
+import datetime
+import logging
+import os
+import re
+import sys
+from MySQLdb import IntegrityError
+
+from openmolar.settings import localsettings
+from openmolar.schema_upgrades.database_updater_thread import DatabaseUpdaterThread
+
+from openmolar.schema_upgrades.druglist import DRUGLIST
+
+LOGGER = logging.getLogger("openmolar")
+
+SQLSTRINGS = [
+ 'drop table if exists medication_link',
+ 'drop table if exists medhist',
+ 'drop table if exists medications',
+ 'update mednotes, (select serialno, max(chgdate) max_date from mnhist group by serialno) tmp_table set mednotes.chkdate = tmp_table.max_date where mednotes.serialno = tmp_table.serialno',
+ '''
+create table medhist (
+ ix int(11) unsigned not null auto_increment ,
+ pt_sno int(11) NOT NULL,
+ medication_comments varchar(200) NOT NULL default "",
+ warning_card varchar(60) NOT NULL default "",
+ allergies varchar(60) NOT NULL default "",
+ respiratory varchar(60) NOT NULL default "",
+ heart varchar(60) NOT NULL default "",
+ diabetes varchar(60) NOT NULL default "",
+ arthritis varchar(60) NOT NULL default "",
+ bleeding varchar(60) NOT NULL default "",
+ infectious_disease varchar(60) NOT NULL default "",
+ endocarditis varchar(60) NOT NULL default "",
+ liver varchar(60) NOT NULL default "",
+ anaesthetic varchar(60) NOT NULL default "",
+ joint_replacement varchar(60) NOT NULL default "",
+ heart_surgery varchar(60) NOT NULL default "",
+ brain_surgery varchar(60) NOT NULL default "",
+ hospital varchar(60) NOT NULL default "",
+ cjd varchar(60) NOT NULL default "",
+ other varchar(60) NOT NULL default "",
+ alert tinyint(1) NOT NULL default 0,
+ chkdate date,
+ modified_by varchar(20) NOT NULL default "unknown",
+ time_stamp timestamp NOT NULL default CURRENT_TIMESTAMP,
+PRIMARY KEY (ix),
+FOREIGN KEY (pt_sno) REFERENCES new_patients(serialno),
+CHECK (allergies=false OR allergies_comment IS NOT NULL)
+)
+''',
+ '''
+create table medications (
+ medication varchar(120) NOT NULL,
+ warning bool NOT NULL DEFAULT false,
+PRIMARY KEY (medication)
+)
+''',
+ '''
+create table medication_link (
+ med_ix int(11) unsigned NOT NULL,
+ med varchar(120),
+ details varchar(60),
+FOREIGN KEY (med_ix) REFERENCES medhist(ix),
+FOREIGN KEY (med) REFERENCES medications(medication)
+)
+''',
+
+]
+
+SOURCE1_QUERY = '''
+select serialno, drnm, adrtel, curmed, oldmed, allerg, heart, lungs, liver,
+kidney, bleed, anaes, other, alert, chkdate from mednotes
+'''
+
+SOURCE2_QUERY = 'select chgdate, ix, note from mnhist where serialno=%s order by chgdate desc'
+
+INSERT_MEDS_QUERY = 'insert into medications (medication) values (%s)'
+
+
+DEST1_QUERY = '''
+INSERT INTO medhist (pt_sno, medication_comments, allergies, respiratory,
+heart, bleeding, liver, anaesthetic, other, alert, chkdate, modified_by)
+values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
+'''
+
+DEST2_QUERY = 'INSERT INTO medication_link (med_ix, med) values (%s, %s)'
+
+
+CLEANUPSTRINGS = [
+]
+
+
+# a class to contain and manipulate the old MH data.
+MedNotes = namedtuple('MedNotes',
+ ('serialno',
+ 'drnm',
+ 'adrtel',
+ 'curmed',
+ 'oldmed',
+ 'allerg',
+ 'heart',
+ 'lungs',
+ 'liver',
+ 'kidney',
+ 'bleed',
+ 'anaes',
+ 'other',
+ 'alert',
+ 'chkdate')
+ )
+
+
+class DatabaseUpdater(DatabaseUpdaterThread):
+
+ def historic_mhs(self, med_notes):
+ prev_mednotes = OrderedDict()
+ prev_mednotes[med_notes.chkdate] = med_notes
+ cursor = self.db.cursor()
+ cursor.execute(SOURCE2_QUERY, (med_notes.serialno,))
+ rows = cursor.fetchall()
+ cursor.close()
+ dates = []
+ for dt, ix, note in rows:
+ if not dt in dates:
+ dates.append(dt)
+ dates.append(None)
+ for changed_dt, ix, note in rows:
+ dt = dates[dates.index(changed_dt) + 1]
+ try:
+ prev_mednote = prev_mednotes[dt]
+ except KeyError:
+ prev_mednote = prev_mednotes.values()[-1]._replace(chkdate=dt)
+ prev_mednotes[dt] = prev_mednote
+ if ix == 142:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(curmed=note)
+ elif ix == 143:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(oldmed=note)
+ elif ix == 144:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(allerg=note)
+ elif ix == 145:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(heart=note)
+ elif ix == 146:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(lungs=note)
+ elif ix == 147:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(liver=note)
+ elif ix == 148:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(bleed=note)
+ elif ix == 149:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(kidney=note)
+ elif ix == 150:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(anaes=note)
+ elif ix == 151:
+ prev_mednotes[dt] = prev_mednotes[dt]._replace(other=note)
+ else:
+ # 140 dr name
+ # 141 dr address
+ # 152 previous chgdate
+ continue
+
+ for mn in reversed(prev_mednotes.values()):
+ yield mn
+
+ def transfer_data(self):
+ '''
+ function specific to this update.
+ '''
+ meds = set()
+ self.progressSig(15, _("inserting medications"))
+ self.cursor.executemany(INSERT_MEDS_QUERY, DRUGLIST)
+
+ self.progressSig(25, _("pulling information from mednotes"))
+ self.cursor.execute(SOURCE1_QUERY)
+ self.progressSig(35, _("inserting information into new tables"))
+ for row in self.cursor.fetchall():
+ med_notes = MedNotes(*row)
+ for mn_hist in self.historic_mhs(med_notes):
+ medications = set()
+ curmed = mn_hist.curmed
+ for meds in curmed.split(" "):
+ for med in meds.split(","):
+ if med.title() in DRUGLIST:
+ medications.add(med.title())
+ curmed = re.sub(
+ "%s,?" % med, "", curmed).strip(" ")
+
+ if curmed:
+ med_comments = "%s: %s" % (_("Unkown medications"), curmed)
+ else:
+ med_comments = ""
+ if mn_hist.oldmed:
+ if med_comments:
+ med_comments += " | "
+ med_comments += "%s: %s" % (
+ _("Previous medications"), mn_hist.oldmed)
+
+ values = (mn_hist.serialno,
+ med_comments,
+ mn_hist.allerg,
+ mn_hist.lungs,
+ mn_hist.heart,
+ mn_hist.bleed,
+ mn_hist.liver,
+ mn_hist.anaes,
+ mn_hist.other,
+ 0 if mn_hist.alert is None else mn_hist.alert,
+ mn_hist.chkdate,
+ "3_0 to 3_1 script"
+ )
+ try:
+ self.cursor.execute(DEST1_QUERY, values)
+ med_ix = self.db.insert_id()
+ for med in medications:
+ self.cursor.execute(DEST2_QUERY, (med_ix, med))
+
+ except IntegrityError:
+ LOGGER.warning(
+ "skipping invalid pt serialno %s", mn_hist.serialno)
+
+ def run(self):
+ LOGGER.info("running script to convert from schema 3.0 to 3.1")
+ try:
+ self.connect()
+ #- execute the SQL commands
+ self.progressSig(10, _("creating new tables"))
+ self.execute_statements(SQLSTRINGS)
+
+ self.transfer_data()
+
+ self.progressSig(75, _("executing cleanup statements"))
+ self.execute_statements(CLEANUPSTRINGS)
+
+ self.progressSig(97, _('updating settings'))
+ LOGGER.info("updating stored database version in settings table")
+
+ self.update_schema_version(("3.1",), "3_0 to 3_1 script")
+
+ self.progressSig(100, _("updating stored schema version"))
+ self.commit()
+ self.completeSig(_("Successfully moved db to") + " 3.1")
+ return True
+ except Exception as exc:
+ LOGGER.exception("error transfering data")
+ self.rollback()
+ raise self.UpdateError(exc)
+
+if __name__ == "__main__":
+ dbu = DatabaseUpdater()
+ if dbu.run():
+ LOGGER.info("ALL DONE, conversion successful")
+ else:
+ LOGGER.warning("conversion failed")
diff --git a/src/openmolar/settings/fee_tables.py b/src/openmolar/settings/fee_tables.py
index e727e48..15ce9dd 100644
--- a/src/openmolar/settings/fee_tables.py
+++ b/src/openmolar/settings/fee_tables.py
@@ -542,9 +542,12 @@ class FeeTable(object):
'''
shortcuts which are used in association with 'other' items
'''
+ items = {}
for item in self.feesDict.values():
if item.pt_attribute == "other":
- yield ("other", item.shortcut)
+ items[item.description.lower()] = item.shortcut
+ for key in sorted(items.keys()):
+ yield ("other", items[key])
class FeeItem(object):
diff --git a/src/openmolar/settings/localsettings.py b/src/openmolar/settings/localsettings.py
index a79660b..1feb2ad 100644
--- a/src/openmolar/settings/localsettings.py
+++ b/src/openmolar/settings/localsettings.py
@@ -45,8 +45,8 @@ SUPERVISOR = "c1219df26de403348e211a314ff2fce58aa6e28d"
DBNAME = "default"
-# updated 27.06.2014
-CLIENT_SCHEMA_VERSION = "2.9"
+# updated 3.9.2014
+CLIENT_SCHEMA_VERSION = "3.1"
DB_SCHEMA_VERSION = "unknown"
@@ -150,22 +150,8 @@ if "win" in sys.platform:
LOGGER.info("Windows OS detected - modifying settings")
#-- sorry about this... but cross platform is a goal :(
global_cflocation = 'C:\\Program Files\\openmolar\\openmolar.conf'
- localFileDirectory = os.path.join(os.environ.get("HOMEPATH"), ".openmolar")
- #-- this next line is necessary because I have to resort to relative
- #-- imports for the css stuff eg... ../resources/style.css
- #-- on linux, the root is always / on windows... ??
- os.chdir(wkdir)
- resources_path = resources_path.replace(
- "://", ":///").replace(" ", "%20").replace("\\", "/")
- stylesheet = stylesheet.replace(
- "://", ":///").replace(" ", "%20").replace("\\", "/")
- printer_png = printer_png.replace(
- "://", ":///").replace(" ", "%20").replace("\\", "/")
- money_png = money_png.replace(
- "://", ":///").replace(" ", "%20").replace("\\", "/")
- LOGOPATH = LOGOPATH.replace(
- "://", ":///").replace(" ", "%20").replace("\\", "/")
-
+ localFileDirectory = os.path.join(
+ os.environ.get("HOMEPATH",""), ".openmolar")
else:
WINDOWS = False
if not "linux" in sys.platform:
@@ -183,11 +169,30 @@ appt_shortcut_file = os.path.join(wkdir, "resources",
"appointment_shortcuts.xml")
stylesheet = "file://" + os.path.join(wkdir, "resources", "style.css")
printer_png = "file://" + os.path.join(wkdir, "resources", "icons", "ps.png")
+medical_png = "file://" + os.path.join(wkdir, "resources", "icons", "med.png")
money_png = "file://" + os.path.join(wkdir, "resources", "icons", "vcard.png")
LOGOPATH = "file://" + os.path.join(wkdir, "html", "images", "newlogo.png")
resources_location = os.path.join(wkdir, "resources")
resources_path = "file://" + resources_location
+if WINDOWS:
+ #-- this next line is necessary because I have to resort to relative
+ #-- imports for the css stuff eg... ../resources/style.css
+ #-- on linux, the root is always / on windows... ??
+
+ os.chdir(wkdir)
+ resources_path = resources_path.replace(
+ "://", ":///").replace(" ", "%20").replace("\\", "/")
+ stylesheet = stylesheet.replace(
+ "://", ":///").replace(" ", "%20").replace("\\", "/")
+ printer_png = printer_png.replace(
+ "://", ":///").replace(" ", "%20").replace("\\", "/")
+ money_png = money_png.replace(
+ "://", ":///").replace(" ", "%20").replace("\\", "/")
+ LOGOPATH = LOGOPATH.replace(
+ "://", ":///").replace(" ", "%20").replace("\\", "/")
+
+
if not os.path.exists(DOCS_DIRECTORY):
os.makedirs(DOCS_DIRECTORY)
@@ -362,7 +367,8 @@ PRACTICE_ADDRESS = ("The Dental Practice", "My Street", "My Town", "POST CODE")
#-- this is updated whenever a patient record loads, for ease of address
#-- manipulation
-LAST_ADDRESS = ("",) * 8
+BLANK_ADDRESS = ("",) * 8
+LAST_ADDRESS = BLANK_ADDRESS
#-- 1 less dialog box for these lucky people
defaultPrinterforGP17 = False
diff --git a/src/openmolar/settings/version.py b/src/openmolar/settings/version.py
index 8597ae0..946ed83 100644
--- a/src/openmolar/settings/version.py
+++ b/src/openmolar/settings/version.py
@@ -27,7 +27,7 @@ This file contains the version number for openmolar
Do not edit this file manually, as it should be updated when git tag is updated.
'''
-VERSION = "0.6.0"
+VERSION = "0.6.2"
if __name__ == '__main__':
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/openmolar.git
More information about the debian-med-commit
mailing list