[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