# #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
a module to search for previous course items
'''
import datetime
import logging
from openmolar.settings import localsettings
from openmolar.connect import connect
from openmolar.dbtools.treatment_course import TreatmentCourse
from openmolar.dbtools import estimatesHistory
from openmolar.dbtools import daybook
from openmolar.ptModules.course_checker import CourseChecker
LOGGER = logging.getLogger("openmolar")
QUERY = '''SELECT courseno FROM currtrtmt2 WHERE serialno=%s
ORDER BY courseno desc, accd desc'''
ALLOW_EDIT = False
def _get_courses(sno, current_csno):
# query allows exclusion of current course.
if current_csno is None:
query = QUERY
values = (sno,)
else:
query = QUERY.replace("ORDER", " AND courseno!=%s ORDER")
values = (sno, current_csno)
db = connect()
cursor = db.cursor()
cursor.execute(query, values)
rows = cursor.fetchall()
cursor.close()
for row in rows:
yield TreatmentCourse(sno, row[0])
def details(sno, current_csno, include_estimates=False, include_daybook=False):
'''
returns an html page showing pt's Treatment History along with estimates
'''
courses = list(_get_courses(sno, current_csno))
estimates_list = estimatesHistory.getEsts(sno) if include_estimates else []
daybook_list = list(daybook.all_data(sno)) if include_daybook else []
daybook_course_guesses = {}
displayed_ests = []
course_checker_errors = 0
html = "%s - %d %s
" % (
_("Past Courses of Treatment"),
len(courses),
_("found")
)
if current_csno is not None:
html += "%s %s %s
" % (
_("Ignoring course number"),
current_csno,
_("as this is active")
)
days_elapsed = None
for i, course in enumerate(courses):
course_html = course.to_html(ALLOW_EDIT, days_elapsed)
course_ests = []
if include_estimates:
est_table_init = False
for est in estimates_list:
if est.courseno == course.courseno:
course_ests.append(est)
if not est_table_init:
header = est.htmlHeader()
if estimatesHistory.ALLOW_EDIT:
header = header.replace(
"",
estimatesHistory.EDIT_STRING % est.courseno)
course_html += (
'%s ' % header)
est_table_init = True
course_html += est.toHtmlRow()
if est_table_init:
course_html += '
\n'
else:
course_html += "%s %d" % (_("no estimate found for courseno"),
course.courseno)
displayed_ests += course_ests
if include_daybook:
daybook_html = ""
if course.accd is None:
accd = datetime.date(1980, 1, 1)
course_html += "%s
" % _(
"Warning - No course acceptance date")
else:
accd = course.accd
if course.cmpd is None:
cmpd = datetime.date.today()
course_html += "%s
" % _(
"Warning - No course completion date, "
"using today to gather daybook items.")
else:
cmpd = course.cmpd
for daybook_entry in daybook_list:
if accd <= daybook_entry.date <= cmpd:
try:
daybook_course_guesses[course.courseno].append(
daybook_entry)
except KeyError:
daybook_course_guesses[
course.courseno] = [
daybook_entry]
gap = cmpd - daybook_entry.date
if daybook.ALLOW_TX_EDITS:
id_col = '%s' % (
daybook_entry.id, _("Edit Tx"))
else:
id_col = str(daybook_entry.id)
daybook_html += "%s |
" % (
" ".join(
(localsettings.formatDate(daybook_entry.date),
daybook_entry.coursetype,
localsettings.ops.get(daybook_entry.dntid),
localsettings.ops.get(daybook_entry.trtid, "-"),
daybook_entry.diagn, daybook_entry.perio,
daybook_entry.anaes, daybook_entry.misc,
daybook_entry.ndu, daybook_entry.ndl,
daybook_entry.odu, daybook_entry.odl, daybook_entry.other,
daybook_entry.chart.strip(chr(0) + " \n"),
localsettings.formatMoney(daybook_entry.feesa),
localsettings.formatMoney(daybook_entry.feesb),
id_col))
)
if daybook_html:
header_rows = daybook.all_data_header()
if course.cmpd is None:
header_rows = header_rows.replace(
"", _("Course is Ongoing"))
elif gap.days != 0:
header_rows = header_rows.replace(
"",
"%s %s %s" % (_("Course closed"),
gap.days,
_("days after last treatment")))
course_html += '' % (
header_rows, daybook_html)
else:
course_html += "%s " % _(
"Course dates not found in daybook")
if include_estimates and include_daybook:
course_check = CourseChecker(
course,
course_ests,
daybook_course_guesses.get(course.courseno, []))
if course_check.has_errors:
course_checker_errors += 1
course_html += course_check.results
course_html += '''
%s''' % (
course.courseno, _("Examine these Issues."))
days_elapsed = ""
try:
prev_course = courses[i + 1]
if ALLOW_EDIT:
merge_link = ' %s?' % (
course.courseno, prev_course.courseno,
_("Merge with previous course")
)
course_html = course_html.replace("", merge_link)
days_elapsed = (course.accd - prev_course.cmpd).days
except IndexError:
days_elapsed = None
pass
except TypeError:
pass
finally:
course_html += '
'
html += course_html
html += ""
orphaned_html = ""
i = 0
for est in estimates_list:
if not est in displayed_ests:
if i == 0:
orphaned_html += '''%s %s
%s ''' % (
_("WARNING"),
_("ORPHANED ESTIMATE DATA"),
est.htmlHeader().replace("#ffff99", "red")
)
orphaned_html += est.toHtmlRow()
i += 1
if course_checker_errors:
html = html.replace(
"",
"%d %s" % (course_checker_errors, _("Errors Found"))
)
if i == 0:
return html
return html.replace("",
"%s %s " % (
orphaned_html,
_("This shouldn't happen!"))
)
if __name__ == "__main__":
from gettext import gettext as _
# ALLOW_EDIT = True
localsettings.initiate()
print details(27107, 0, True, True).encode("ascii", "replace")
openmolar-0.6.2/src/openmolar/dbtools/day_class.py 0000644 0001750 0001750 00000004617 12320217277 022173 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
1 # -*- coding: utf-8 -*-
# Copyright (c) 2009 Neil Wallace. All rights reserved.
# This program or module 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. See the GNU General Public License for more details.
'''unused module'''
import MySQLdb
import datetime
from openmolar.connect import connect
from openmolar.settings import localsettings
class day():
def init(self, adate, starttime="", endtime=""):
self.date = adate
self.starttime = starttime
self.endtime = endtime
if __name__ == "__main__":
d = day(datetime.date(1969, 12, 9), "08:30", "18:00")
openmolar-0.6.2/src/openmolar/dbtools/daybook.py 0000644 0001750 0001750 00000032363 12455427152 021664 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
this module provides read/write tools for the daybook database table
'''
from collections import namedtuple
import logging
from PyQt4.QtCore import QDate
from openmolar.settings import localsettings
from openmolar import connect
ALLOW_TX_EDITS = False
LOGGER = logging.getLogger("openmolar")
QUERY = '''insert into daybook
(date, serialno, coursetype, dntid, trtid, diagn, perio, anaes,
misc,ndu,ndl,odu,odl,other,chart,feesa,feesb,feesc)
values (DATE(NOW()),%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'''
HASH_QUERY = 'insert into daybook_link (daybook_id, tx_hash) values (%s, %s)'
INSPECT_QUERY = '''select description, fee, ptfee
from newestimates join est_link2 on newestimates.ix = est_link2.est_id
where tx_hash in
(select tx_hash from daybook join daybook_link on daybook.id = daybook_link.daybook_id where id=%s)'''
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 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("%", "%%"))
DAYBOOK_QUERY = '''select date, coursetype, dntid,
trtid, diagn, perio, anaes, misc, ndu, ndl, odu, odl, other, chart,
feesa, feesb, feesc, id
from daybook where serialno=%s order by date'''
FIELD_NAMES_QUERY = '''
SELECT concat(table_name, ".", column_name) as fieldname FROM
information_schema.columns
WHERE table_name='patients' or table_name="daybook" order by fieldname'''
UPDATE_ROW_FEES_QUERY = "update daybook set feesa=%s, feesb=%s where id=%s"
UPDATE_ROW_FEE_QUERY = "update daybook set feesa=%s where id=%s"
UPDATE_ROW_PTFEE_QUERY = "update daybook set feesb=%s where id=%s"
DELETE_ROW_QUERY = "delete from daybook where id=%s"
TREATMENTS_QUERY = ('select diagn, perio, anaes, misc, ndu, ndl, '
'odu, odl, other, chart from daybook where id = %s')
UPDATE_TREATMENTS_QUERY = ('update daybook '
'set diagn=%s, perio=%s, anaes=%s, misc=%s, ndu=%s, ndl=%s, '
'odu=%s, odl=%s, other=%s, chart=%s where id = %s')
# custom class for daybook data
DaybookEntry = namedtuple('DaybookEntry',
('date', 'coursetype', 'dntid', 'trtid', 'diagn', 'perio',
'anaes', 'misc', 'ndu', 'ndl', 'odu', 'odl', 'other', 'chart',
'feesa', 'feesb', 'feesc', 'id')
)
def add(sno, cset, dent, trtid, t_dict, fee, ptfee, tx_hashes):
'''
add a row to the daybook table
'''
if trtid in (0, None):
LOGGER.warning("no clinician login - daybook will contain junk!")
db = connect.connect()
cursor = db.cursor()
values = (sno, cset, dent, trtid, t_dict["diagn"], t_dict["perio"],
t_dict["anaes"], t_dict["misc"], t_dict["ndu"], t_dict["ndl"],
t_dict["odu"], t_dict["odl"], t_dict["other"], t_dict["chart"],
fee, ptfee, 0)
LOGGER.debug('updating daybook with the following values: '
'%s %s %s %s %s %s %s %s' % (
sno, cset, dent, trtid, t_dict, fee, ptfee, 0))
cursor.execute(QUERY, values)
daybook_id = db.insert_id()
for tx_hash in tx_hashes:
LOGGER.debug("%s %s %s" % (HASH_QUERY, daybook_id, tx_hash))
cursor.execute(HASH_QUERY, (daybook_id, tx_hash))
cursor.close()
def details(regdent, trtdent, startdate, enddate, filters=""):
'''
returns an html table, for regdent, trtdent,startdate,enddate
'''
dent_conditions = ""
dents = []
try:
if regdent != "*ALL*":
dent_conditions = 'dntid=%s and '
dents.append(localsettings.ops_reverse[regdent])
if trtdent != "*ALL*":
dent_conditions += 'trtid=%s and '
dents.append(localsettings.ops_reverse[trtdent])
except KeyError:
print "Key Error - %s or %s unregconised" % (regdent, trtdent)
return '%s' % _(
"Error - unrecognised practioner- sorry")
total, nettotal = 0, 0
iterDate = QDate(startdate.year(), startdate.month(), 1)
retarg = '''
%s %s %s %s %s %s %s %s %s''' % (
_("Patients of"), regdent, _("treated by"), trtdent, _("between"),
localsettings.formatDate(startdate.toPyDate()), _("and"),
localsettings.formatDate(enddate.toPyDate()), filters)
retarg += '''DATE |
Dents | Serial Number | Name |
Pt Type | Treatment | |
Gross Fee | Net Fee | '''
db = connect.connect()
cursor = db.cursor()
query = DETAILS_QUERY.replace("{{DENT CONDITIONS}}", dent_conditions)
query = query.replace("{{FILTERS}}", filters)
while enddate >= iterDate:
monthtotal, monthnettotal = 0, 0
if startdate > iterDate:
queryStartDate = startdate
else:
queryStartDate = iterDate
queryEndDate = iterDate.addMonths(1).addDays(-1)
if enddate < queryEndDate:
queryEndDate = enddate
values = tuple(
dents + [queryStartDate.toPyDate(), queryEndDate.toPyDate()])
cursor.execute(query, (values))
rows = cursor.fetchall()
for i, row in enumerate(rows):
retarg += '
---|
' if i % 2 else ' '
retarg += "%s | " % row[0]
try:
retarg += ' %s / ' % localsettings.ops[row[4]]
except KeyError:
retarg += " | ?? / "
try:
retarg += localsettings.ops[row[5]]
except KeyError:
retarg += "??"
retarg += ' | %s | %s | %s | ' % (row[1:4])
tx = ""
for item in (6, 7, 8, 9, 10, 11, 12, 13, 14, 15):
if row[item] is not None and row[item] != "":
tx += "%s " % row[item]
if ALLOW_TX_EDITS:
extra_link = ' / %s' % (
row[19], _("Edit Tx"))
else:
extra_link = ""
retarg += '''%s |
%s%s |
%s |
%s | ''' % (tx.strip("%s " % chr(0)),
row[19], row[16], row[17],
_("Ests"),
extra_link,
localsettings.formatMoney(
row[16]),
localsettings.formatMoney(row[17]))
total += int(row[16])
monthtotal += int(row[16])
nettotal += int(row[17])
monthnettotal += int(row[17])
retarg += ''' | SUBTOTAL - %s %s |
%s |
%s | ''' % (
localsettings.monthName(iterDate.toPyDate()),
iterDate.year(),
localsettings.formatMoney(monthtotal),
localsettings.formatMoney(monthnettotal))
iterDate = iterDate.addMonths(1)
cursor.close()
# db.close()
retarg += ''' | GRAND TOTAL |
%s |
%s | ''' % (
localsettings.formatMoney(total), localsettings.formatMoney(nettotal))
return retarg
def inspect_item(id):
'''
get more detailed information (by polling the newestimates table
'''
db = connect.connect()
cursor = db.cursor()
cursor.execute(INSPECT_QUERY, (id, ))
rows = cursor.fetchall()
cursor.close()
return rows
def get_treatments(id):
'''
get more detailed information (by polling the newestimates table
'''
db = connect.connect()
cursor = db.cursor()
cursor.execute(TREATMENTS_QUERY, (id, ))
row = cursor.fetchone()
cursor.close()
return row
def update_treatments(id, treatments):
values = list(treatments) + [id]
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(UPDATE_TREATMENTS_QUERY, values)
cursor.close()
return result
def update_row_fees(id, feesa, feesb):
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(UPDATE_ROW_FEES_QUERY, (feesa, feesb, id))
cursor.close()
return result
def update_row_fee(id, feesa):
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(UPDATE_ROW_FEE_QUERY, (feesa, id))
cursor.close()
return result
def update_row_ptfee(id, feesb):
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(UPDATE_ROW_PTFEE_QUERY, (feesb, id))
cursor.close()
return result
def delete_row(id):
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(DELETE_ROW_QUERY, (id,))
cursor.close()
return result
def all_data(serialno):
db = connect.connect()
cursor = db.cursor()
cursor.execute(DAYBOOK_QUERY, (serialno,))
rows = cursor.fetchall()
cursor.close()
for row in rows:
yield DaybookEntry(*row)
def all_data_header():
color_string = ' bgcolor="#ffff99"'
return '''
%s | |
%s |
''' % (color_string,
_("Daybook Items during this Period"),
color_string, color_string,
("" % color_string).join(
("date", "cset", "dntid", "trtid",
"diagn", "perio", "anaes", "misc",
"ndu", "ndl", "odu", "odl", "other",
"chart", "feesa", "feesb", "id")
)
)
class FilterHelp(object):
_field_names = None
@property
def field_names(self):
if self._field_names is None:
db = connect.connect()
cursor = db.cursor()
cursor.execute(FIELD_NAMES_QUERY)
self._field_names = cursor.fetchall()
cursor.close()
return self._field_names
def help_text(self):
'''
text to be shown in user clicks on the "filter help button"
'''
html = "%s%s %s" % (
_("Filter your results"),
_("If this text box is left blank, then results from the daybook are "
"returned dependent on the dates and clinicians entered."),
_("You can filter using the following fields.")
)
for i, field in enumerate(self.field_names):
if i == 0:
html += ""
elif i % 5 == 0:
html += " "
html += "%s | " % field
html += " %s%s " % (
_("Examples"),
'patients.serialno=1 AND chart REGEXP ".*MOD,CO.*"\n'
'ndu="SR/F"\nexmpt="M"\n')
return html
_filter_help = FilterHelp()
def filter_help_text():
return _filter_help.help_text()
if __name__ == "__main__":
localsettings.initiate()
for combo in (("*ALL*", "NW"), ("NW", "AH"), ("NW", "NW")):
r = details(combo[0], combo[1], QDate(
2008, 10, 31), QDate(2008, 11, 11))
print r.encode("ascii", "replace")
print filter_help_text()
openmolar-0.6.2/src/openmolar/dbtools/daybookHistory.py 0000644 0001750 0001750 00000010432 12350030721 023221 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from __future__ import division
from openmolar.settings import localsettings
from openmolar.connect import connect
QUERY = '''select DATE_FORMAT(date, '%s'), coursetype,
dntid, trtid, concat(diagn,perio,anaes,misc,ndu,ndl,odu,odl),
other,chart,feesa,feesb, id from daybook
where serialno = %%s order by date desc, id desc
''' % localsettings.OM_DATE_FORMAT.replace("%", "%%")
ALLOW_TX_EDITS = False
def details(sno):
'''
returns an html page showing pt's Treatment History
'''
db = connect()
cursor = db.cursor()
cursor.execute(QUERY, (sno,))
rows = cursor.fetchall()
cursor.close()
claimNo = len(rows)
retarg = "Past Treatments - %d rows found" % claimNo
if claimNo == 0:
return retarg
headers = ("Date", "Csetype", "Dentist", "Clinician",
"Treatment", "Chart", "", "Fee", "PtCharge")
retarg += ''
for header in headers:
retarg += "%s | " % header
retarg += ' '
fee_total, ptfee_total = 0, 0
for i, (
date_, cset, dnt, trt, tx, tx1, tx2, fee, ptfee, id) in enumerate(rows):
if tx1 is not None:
#-- the "other treatment" column allows nulls,
#-- which stuffs up the sql concat
tx += tx1
retarg += ' ' if i % 2 else ' '
if ALLOW_TX_EDITS:
extra_link = ' / %s' % (
id, _("Edit Tx"))
else:
extra_link = ""
retarg += '''\n %s |
%s |
%s |
%s |
%s |
%s |
%s%s
|
%s | %s | \n \n''' % (
date_, cset,
localsettings.ops.get(dnt),
localsettings.ops.get(trt),
tx, tx2.strip("\x00"),
id, fee, ptfee, _("Ests"),
extra_link,
localsettings.formatMoney(fee),
localsettings.formatMoney(ptfee)
)
fee_total += fee
ptfee_total += ptfee
retarg += '''
|
TOTALS |
%s |
%s | \n \n ''' % (
localsettings.formatMoney(fee_total),
localsettings.formatMoney(ptfee_total))
return retarg
if __name__ == "__main__":
localsettings.initiate()
print''
print details(17322).encode("ascii", errors="replace")
print ""
openmolar-0.6.2/src/openmolar/dbtools/db_notes.py 0000644 0001750 0001750 00000004304 12320217277 022017 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
module to retrieve from the new formatted_notes table
'''
from openmolar.connect import connect
def notes(serialno, today_only=False):
query = '''SELECT ndate, op1, op2, ntype, note
from formatted_notes where serialno = %s and ndate = DATE(NOW())
order by ndate, ix'''
if not today_only:
query = query.replace("and ndate = DATE(NOW())", "")
db = connect()
cursor = db.cursor()
cursor.execute(query, (serialno,))
results = cursor.fetchall()
cursor.close()
return results
if __name__ == "__main__":
print notes(1)
openmolar-0.6.2/src/openmolar/dbtools/db_patients.py 0000644 0001750 0001750 00000004442 12353117723 022522 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
module to retrieve from the patients table
note - PatientClass itself does most of this
'''
from openmolar.connect import connect
from openmolar.settings.localsettings import PatientNotFoundError
def name(serialno):
query = 'SELECT title, fname, sname from new_patients where serialno = %s'
db = connect()
cursor = db.cursor()
cursor.execute(query, (serialno,))
result = cursor.fetchone()
cursor.close()
if not result:
raise PatientNotFoundError("Serialno %s not found in database")
title, fname, sname = result
return "%s %s %s (%s)" % (title, fname, sname, serialno)
if __name__ == "__main__":
print name(41)
openmolar-0.6.2/src/openmolar/dbtools/db_settings.py 0000644 0001750 0001750 00000026305 12455453236 022542 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
this module reads and write to the settings table of the database
'''
import datetime
import logging
import re
from openmolar import connect
from openmolar.settings import localsettings
LOGGER = logging.getLogger("openmolar")
PT_COUNT_QUERY = "select count(*) from new_patients"
# PRACTITIONERS_QUERY = "select id, inits, apptix from practitioners"
DENTIST_DATA_QUERY = "select id,inits,name,formalname,fpcno,quals from practitioners where flag0=1"
# APPTIX_QUERY = "select apptix,inits from practitioners where flag3=1"
# ACTIVE_DENTS_QUERY = "select apptix, inits from practitioners where flag3=1 and flag0=1"
# ACTIVE_HYGS_QUERY = "select apptix, inits from practitioners where
# flag3=1 and flag0=0"
CLINICIANS_QUERY = '''
SELECT ix, apptix, initials, name, formal_name, qualifications, type,
speciality, data, start_date, end_date FROM
clinicians JOIN clinician_dates on clinicians.ix = clinician_dates.clinician_ix
LEFT JOIN diary_link on ix = diary_link.clinician_ix
'''
ACTIVE_CLINICIANS_QUERY = CLINICIANS_QUERY + \
'''WHERE start_datenow());'''
LOGINS_QUERY = "select id from opid"
INSERT_OPID_QUERY = "INSERT INTO opid (id) values (%s)"
INSERT_CLINICIAN_QUERIES = (
'''INSERT INTO clinicians
(initials, name, formal_name, qualifications, type, speciality, data, comments)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s)
''',
'''
INSERT INTO clinician_dates(clinician_ix, start_date, end_date)
VALUES (%s, %s, %s)
''',
'''
INSERT INTO diary_link(clinician_ix, apptix)
VALUES (%s, %s)
''')
INSERT_SETTING_QUERY = \
'''INSERT INTO settings (value, data, modified_by, time_stamp)
values (%s, %s, %s, NOW())'''
UPDATE_SETTING_QUERY = \
'''UPDATE settings SET data = %s, modified_by = %s, time_stamp = NOW()
where value=%s'''
def insert_login(opid):
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(INSERT_OPID_QUERY, (opid,))
cursor.close()
return result
def insertData(value, data, user=None):
'''
insert a setting (leaving old values behind)
'''
LOGGER.info("saving setting (%s, %s) to settings table", value, data)
if user is None:
user = localsettings.operator
values = (value, data, user)
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(INSERT_SETTING_QUERY, values)
cursor.close()
return True
def updateData(value, data, user=None):
'''
update a setting - if no update occurs, will insert
'''
LOGGER.info("updating setting (%s, %s) to settings table", value, data)
if user is None:
user = localsettings.operator
values = (data, user, value)
db = connect.connect()
cursor = db.cursor()
if cursor.execute(UPDATE_SETTING_QUERY, values):
cursor.close()
return True
return insertData(value, data, user)
def insert_practice_name(practice_name):
return insertData("practice name", practice_name)
def insert_practice_address(address):
return insertData("practice address", address)
def insert_clinician(clinician):
result = False
comments = "added by client - %s" % datetime.datetime.now().strftime(
"%m %h %Y %H:%M")
db = connect.connect()
try:
db.autocommit = False
cursor = db.cursor()
cursor.execute(INSERT_CLINICIAN_QUERIES[0],
(clinician.initials,
clinician.name,
clinician.formal_name,
clinician.qualifications,
clinician.type,
clinician.speciality,
clinician.data,
comments)
)
ix = db.insert_id()
cursor.execute(INSERT_CLINICIAN_QUERIES[1], (ix,
clinician.start_date,
clinician.end_date)
)
if clinician.new_diary:
cursor.execute(INSERT_CLINICIAN_QUERIES[2], (ix, ix))
cursor.close()
db.commit()
result = True
except:
LOGGER.exception("failed to insert clinician")
db.rollback()
finally:
db.autocommit = True
return result
class SettingsFetcher(object):
def __init__(self):
self._cursor = None
self.loaded = False
self.PT_COUNT = 0
@property
def cursor(self):
if self._cursor is None:
db = connect.connect()
self._cursor = db.cursor()
return self._cursor
def close_cursor(self):
if self._cursor is not None:
self._cursor.close()
self._cursor = None
def fetch(self):
self.cursor.execute(PT_COUNT_QUERY)
self.PT_COUNT = self.cursor.fetchone()[0]
self._get_clinicians()
self.loaded = True
self.close_cursor()
def getData(self, key):
try:
query = 'select data from settings where value = %s order by ix'
self.cursor.execute(query, (key,))
rows = self.cursor.fetchall()
return rows
except connect.ProgrammingError:
return ()
def get_unique_value(self, key):
'''
get a single value from the settings table.
by default gets the last entry
'''
try:
return self.getData(key)[-1][0]
except IndexError:
LOGGER.warning("no key '%s' found in settings", key)
@property
def allowed_logins(self):
self.cursor.execute(LOGINS_QUERY)
# grab initials of those currently allowed to log in
trows = self.cursor.fetchall()
allowed_logins = []
for row in trows:
allowed_logins.append(row[0])
return allowed_logins
@property
def wiki_url(self):
'''
the database may know of the url (presumably an internally facing ip)
for the practice wiki??
'''
wiki_url = self.get_unique_value("wikiurl")
return wiki_url if wiki_url else "http://openmolar.com/wiki"
@property
def book_end(self):
book_end = self.get_unique_value("bookend")
try:
year, month, day = book_end.split(",")
return datetime.date(int(year), int(month), int(day))
except AttributeError:
pass
except ValueError:
LOGGER.warning("Badly formatted value for bookend in settings")
return datetime.date.today() + datetime.timedelta(days=183)
@property
def practice_name(self):
name = self.get_unique_value("practice name")
if name:
return name
return _("Example Dental Practice")
@property
def practice_address(self):
address = self.get_unique_value("practice address")
address_list = [self.practice_name]
try:
for line_ in address.split("|"):
address_list.append(line_)
except AttributeError:
address_list += ["My Street", "My Town", "POST CODE"]
except ValueError:
LOGGER.warning(
"Badly formatted value for practice_address in settings")
address_list.append(unicode(address))
return tuple(address_list)
@property
def supervisor_pword(self):
hash_ = self.get_unique_value("supervisor_pword")
if hash_:
return hash_
LOGGER.warning("#" * 30)
LOGGER.warning("WARNING - no supervisor password is set")
LOGGER.warning("#" * 30)
# hash of salted ""
return "c1219df26de403348e211a314ff2fce58aa6e28d"
def _get_clinicians(self):
'''
poll the database and retrieve all practitioners (past and present)
'''
self.ops, self.ops_reverse = {}, {}
self.apptix_dict, self.apptix_reverse = {}, {}
active_dent_initials, active_dent_ixs = [], []
active_hyg_initials, active_hyg_ixs = [], []
self.dentist_data = {}
self.cursor.execute(CLINICIANS_QUERY)
rows = self.cursor.fetchall()
for (ix, apptix, initials, name, formal_name, qualifications, type_,
speciality, data, start_date, end_date) in rows:
self.ops[ix] = initials
self.ops_reverse[initials] = ix
today = datetime.date.today()
if apptix:
self.apptix_reverse[apptix] = initials
if start_date <= today and (end_date is None or end_date >= today):
if apptix:
self.apptix_dict[initials] = apptix
if type_ == 1:
active_dent_initials.append(initials)
active_dent_ixs.append(ix)
elif type_ in (2, 3): # hygienist and therapist
active_hyg_initials.append(initials)
active_hyg_ixs.append(ix)
if type_ == 1:
list_no = ""
if data:
m = re.search("list_no=([^ ]*)", data)
if m:
list_no = m.groups()[0]
self.dentist_data[ix] = (
initials,
name,
formal_name,
list_no,
qualifications)
self.active_dents = tuple(active_dent_initials), tuple(active_dent_ixs)
self.active_hygs = tuple(active_hyg_initials), tuple(active_hyg_ixs)
if __name__ == "__main__":
sf = SettingsFetcher()
sf.fetch()
print sf.PT_COUNT
print sf.wiki_url
print sf.book_end
print sf.supervisor_pword
print sf.getData("enddate")
print sf.active_dents
print sf.active_hygs
print sf.dentist_data
openmolar-0.6.2/src/openmolar/dbtools/distinct_statuses.py 0000644 0001750 0001750 00000004652 12353117723 024005 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
from openmolar import connect
LOGGER = logging.getLogger("openmolar")
QUERY = "select distinct status from new_patients"
class DistinctStatuses(object):
_distinct_statuses = None
@property
def DISTINCT_STATUSES(self):
if self._distinct_statuses is None:
db = connect.connect()
cursor = db.cursor()
cursor.execute(QUERY)
rows = cursor.fetchall()
cursor.close()
self._distinct_statuses = set(["", _("DECEASED")])
for row in sorted(rows):
if row[0] not in (None, "BAD DEBT"):
self._distinct_statuses.add(row[0])
return sorted(self._distinct_statuses)
if __name__ == "__main__":
ds = DistinctStatuses()
print ds.DISTINCT_STATUSES
openmolar-0.6.2/src/openmolar/dbtools/docsimported.py 0000644 0001750 0001750 00000010103 12320217277 022710 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import os
import mimetypes
import datetime
from openmolar import connect
from openmolar.settings import localsettings
def getData(ix):
'''
gets the binary data for the file from the database,
along with the version number
'''
db = connect.connect()
cursor = db.cursor()
query = '''select filedata from docsimporteddata where masterid=%s''' % ix
cursor.execute(query)
rows = cursor.fetchall()
cursor.close()
if rows:
return rows
else:
return (("no data found",),)
def storedDocs(sno):
'''
find previously printed docs related to the serialno given as the argument
'''
db = connect.connect()
cursor = db.cursor()
query = '''select DATE_FORMAT(filedate,'%s'), name, size, datatype, ix
from docsimported where serialno=%s order by ix DESC ''' % (
localsettings.OM_DATE_FORMAT, sno)
cursor.execute(query)
rows = cursor.fetchall()
cursor.close()
docs = []
for fdate, fname, fsize, typ, ix in rows:
docs.append([fdate, fname, sizeof_fmt(fsize), typ, str(ix)])
return docs
def chunks_from_file(filepath, chunksize=57344):
'''
a generator to break a file into chunks
'''
f = open(filepath, "rb")
while True:
chunk = f.read(chunksize)
if chunk:
yield chunk
else:
break
f.close()
def sizeof_fmt(num):
for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
if num < 1024.0:
return "%3.1f%s" % (num, x)
num /= 1024.0
def add(sno, filepath):
'''
add a binary file to the database (broken into chunks)
'''
st = os.stat(filepath)
db = connect.connect()
cursor = db.cursor()
query = '''insert into docsimported
(serialno, datatype, name, size, filedate) values (%s, %s, %s, %s, %s)'''
file_type = mimetypes.guess_type(filepath)[0]
if file_type is None:
file_type = "unknown"
values = (sno, file_type, os.path.basename(filepath), st.st_size,
datetime.datetime.fromtimestamp(st.st_mtime))
cursor.execute(query, values)
fileid = db.insert_id()
query = 'INSERT INTO docsimporteddata (masterid, filedata) VALUES (%s, %s)'
for data in chunks_from_file(filepath):
values = (fileid, data)
cursor.execute(query, values)
print "added doc to importeddocs table"
db.commit()
cursor.close()
if __name__ == "__main__":
#- test function
data = getData(1)
print data
openmolar-0.6.2/src/openmolar/dbtools/docsprinted.py 0000644 0001750 0001750 00000006067 12320217277 022550 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from openmolar import connect
from openmolar.settings import localsettings
def getData(ix):
'''
gets the binary data for the file from the database,
along with the version number
'''
db = connect.connect()
cursor = db.cursor()
query = '''select data, docversion from newdocsprinted where ix=%d''' % ix
cursor.execute(query)
rows = cursor.fetchone()
cursor.close()
return rows
def previousDocs(sno):
'''
find previously printed docs related to the serialno given as the argument
'''
db = connect.connect()
cursor = db.cursor()
query = '''select DATE_FORMAT(printdate,'%s'),docname,docversion,ix
from newdocsprinted where serialno=%s order by ix DESC ''' % (
localsettings.OM_DATE_FORMAT, sno)
cursor.execute(query)
rows = cursor.fetchall()
cursor.close()
# db.close()
return rows
def add(sno, docname, object, version=1):
'''
add a note in the database of stuff which has been printed
'''
db = connect.connect()
cursor = db.cursor()
query = '''INSERT INTO newdocsprinted
(serialno,printdate,docname,docversion,data)
VALUES (%s, date(NOW()), %s, %s, %s)'''
values = (sno, docname, version, object)
print "adding letter to newdocsprinted table"
cursor.execute(query, values)
db.commit()
cursor.close()
if __name__ == "__main__":
#- test function
data, version = getData(80982)
print data, version
openmolar-0.6.2/src/openmolar/dbtools/est_logger.py 0000644 0001750 0001750 00000012267 12320275417 022363 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
this module provides read/write tools for the est_logger database table
'''
import logging
from PyQt4.QtCore import QDate
from openmolar.settings import localsettings
from openmolar.connect import connect
LOGGER = logging.getLogger("openmolar")
SELECT_QUERY = ('select est_data from est_logger '
'where courseno=%s order by ix desc limit 1')
INSERT_QUERY = ('insert into est_logger '
'(courseno, est_data, operator) values (%s,%s,%s)')
HISTORY_QUERY = ('select est_data, operator, time_stamp '
'from est_logger where courseno=%s')
class EstLogger(object):
def __init__(self, courseno):
self.courseno = courseno
self.est_data = ""
self.get_data()
def get_data(self):
db = connect()
cursor = db.cursor()
LOGGER.debug(
'getting last estimate text from est_logger for courseno %s' % (
self.courseno))
cursor.execute(SELECT_QUERY, (self.courseno,))
try:
self.est_data = cursor.fetchone()[0]
except TypeError:
pass
cursor.close()
def add_row(self, courseno, est_data):
'''
add a row to the daybook table, and save state.
'''
if courseno and self._write_needed(courseno, est_data):
db = connect()
cursor = db.cursor()
LOGGER.debug('updating est_logger for courseno %s' % courseno)
values = (courseno, est_data, localsettings.operator)
cursor.execute(INSERT_QUERY, values)
cursor.close()
self.courseno = courseno
self.est_data = est_data
else:
LOGGER.debug("est_logger up to date")
def _write_needed(self, courseno, est_data):
return courseno !=self. courseno or est_data != self.est_data
def html_history(courseno):
db = connect()
cursor = db.cursor()
cursor.execute(HISTORY_QUERY, (courseno,))
rows = cursor.fetchall()
cursor.close()
if not rows:
return u'''
%s %s
''' % (_("No estimate history found for course"), courseno)
html = u'''
%s
''' % _("Current Estimate Version History")
html += u'''
%s |
%s |
''' % (
_("Estimate"),
_("Author")
)
for est_data, author, time_stamp in rows:
lines = est_data.split("||\n")
formatted_est = '''
%s | %s | %s | %s | %s | %s |
%s | %s | ''' % (
_("No."), _("Itemcode"), _("Description"), "CseTyp", _("Feescale"),
_("Dentist"), _("Fee"), _("Charge"))
for line in lines:
formatted_est += ""
for i, field in enumerate(line.split(" || ")):
align = 'align="center"' if i < 6 else 'align="right"'
formatted_est += "%s | " % (align, field)
formatted_est += " "
html += u'''
%s |
%s %s |
''' % (
formatted_est, author, time_stamp)
return html + " "
if __name__ == "__main__":
localsettings.initiate()
LOGGER.setLevel(logging.DEBUG)
est_logger = EstLogger(1)
est_logger.add_row(1, "test_data")
print html_history(1)
openmolar-0.6.2/src/openmolar/dbtools/estimate_synopsis.py 0000644 0001750 0001750 00000007227 12350030721 024001 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from openmolar.settings import localsettings
from openmolar.connect import connect
QUERY = '''SELECT description, ptfee, est_link2.completed
from newestimates right join est_link2 on newestimates.ix = est_link2.est_id
where serialno=%s AND courseno=%s order by itemcode, description'''
def html(serialno, courseno):
values = (serialno, courseno)
db = connect()
cursor = db.cursor()
cursor.execute(QUERY, values)
rows = cursor.fetchall()
cursor.close()
est_count = len(rows)
if est_count == 0:
return "No Estimate Found"
completed, planned = [], []
for description, fee, comp in rows:
if comp:
completed.append(
(description, fee, localsettings.formatMoney(fee)))
else:
planned.append((description, fee, localsettings.formatMoney(fee)))
n_rows = len(planned)
if len(completed) > n_rows:
n_rows = len(completed)
html_ = '''
Planned |
|
Completed |
'''
c_tot, p_tot = 0, 0
for i in range(n_rows):
try:
c_desc, fee, c_fee = completed[i]
c_tot += fee
except IndexError:
c_desc, c_fee = "", ""
try:
p_desc, fee, p_fee = planned[i]
p_tot += fee
except IndexError:
p_desc, p_fee = "", ""
html_ += '''
%s |
%s |
|
%s |
%s |
''' % (
p_desc, p_fee, c_desc, c_fee)
html_ += '''
%s |
|
%s |
''' % (
localsettings.formatMoney(p_tot), localsettings.formatMoney(c_tot))
return html_ + " "
if __name__ == "__main__":
print html(41146).encode("ascii", "replace")
openmolar-0.6.2/src/openmolar/dbtools/estimates.py 0000644 0001750 0001750 00000022046 12455454316 022231 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
from openmolar import connect
from openmolar.settings import localsettings
from openmolar.ptModules.estimates import TXHash, Estimate
LOGGER = logging.getLogger("openmolar")
ESTS_QUERY = '''SELECT newestimates.ix, number, itemcode, description,
fee, ptfee, feescale, csetype, dent, est_link2.completed, tx_hash, courseno
from newestimates right join est_link2 on newestimates.ix = est_link2.est_id
where serialno=%s and courseno=%s order by itemcode, ix'''
ESTS_INS_QUERY = ('insert into newestimates (serialno, '
'courseno, number, itemcode, description, fee, ptfee, feescale, '
'csetype, dent, modified_by, time_stamp) values '
'(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW())')
EST_LINK_INS_QUERY = (
'insert into est_link2 (est_id, tx_hash, completed) values (%s, %s, %s)')
EST_DEL_QUERY = "delete from newestimates where ix=%s"
EST_LINK_DEL_QUERY = "delete from est_link2 where est_id=%s"
ESTS_UPDATE_QUERY = '''UPDATE newestimates SET
number=%s, itemcode=%s, description=%s, fee=%s, ptfee=%s, feescale=%s,
csetype=%s, dent=%s, modified_by=%s, time_stamp=NOW() WHERE ix=%s
'''.replace("\n", " ")
# too risky not to check these are unique before updating.
EST_DAYBOOK_ALTERATION_QUERIES = [
'select daybook_id from daybook_link where tx_hash = %s',
'''select sum(fee), sum(ptfee) from newestimates join est_link2
on newestimates.ix = est_link2.est_id where tx_hash in
(select tx_hash from daybook join daybook_link
on daybook.id = daybook_link.daybook_id where id=%s)''',
'update daybook set feesa = %s, feesb = %s where serialno=%s and id=%s'
]
def get_ests(serialno, courseno):
'''
get estimate data
'''
db = connect.connect()
cursor = db.cursor()
cursor.execute(ESTS_QUERY, (serialno, courseno))
rows = cursor.fetchall()
ests = []
for row in rows:
hash_ = row[10]
completed = bool(row[9])
tx_hash = TXHash(hash_, completed)
ix = row[0]
found = False
# use existing est if one relates to multiple treatments
for existing_est in ests:
if existing_est.ix == ix:
existing_est.tx_hashes.append(tx_hash)
found = True
break
if found:
continue
# initiate a custom data class
est = Estimate()
est.ix = ix
est.courseno = row[11]
est.number = row[1]
est.itemcode = row[2]
est.description = row[3]
est.fee = row[4]
est.ptfee = row[5]
est.feescale = row[6]
est.csetype = row[7]
est.dent = row[8]
est.tx_hashes = [tx_hash]
ests.append(est)
cursor.close()
return ests
def update_daybook_after_estimate_change(values):
'''
if the value of a treatment item has been changed after completion,
update the daybook.
most common example of this is when an exemption is applied to a course of
treatment at reception (altering the charges put into the system in the
surgery)
note - use of serialno here is purely for precautionary reasons.
Hash collisions shouldn't occur... but easy to be cautious here.
'''
serialno, tx_hash = values
db = connect.connect()
cursor = db.cursor()
query = EST_DAYBOOK_ALTERATION_QUERIES[0]
cursor.execute(query, (tx_hash.hash,))
rows = cursor.fetchall()
if len(rows) != 1:
LOGGER.warning(
"unable to update daybook after estimate change - abandoning")
return
daybook_id = rows[0][0]
LOGGER.debug("updating daybook row %s" % daybook_id)
query = EST_DAYBOOK_ALTERATION_QUERIES[1]
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))
query = EST_DAYBOOK_ALTERATION_QUERIES[2]
rows_changed = cursor.execute(
query, (feesa, feesb, serialno, daybook_id))
LOGGER.info("changes applied = %s" % bool(rows_changed))
def apply_changes(pt, old_ests, new_ests):
LOGGER.info("APPLY ESTIMATE CHANGES")
estimate_insertions = []
estimate_updates = []
estimate_deletions = []
post_cleanup_commands = []
result = True
old_ests_dict = {}
for est in old_ests:
if est.ix is not None:
old_ests_dict[est.ix] = est
for est in new_ests:
if est.ix is None: # --new item
values = (pt.serialno, est.courseno, est.number,
est.itemcode, est.description,
est.fee, est.ptfee, est.feescale, est.csetype,
est.dent, localsettings.operator)
estimate_insertions.append((ESTS_INS_QUERY, values, est.tx_hashes))
elif est.ix in old_ests_dict.keys():
oldEst = old_ests_dict[est.ix]
if oldEst != est:
values = (est.number,
est.itemcode, est.description,
est.fee, est.ptfee, est.feescale, est.csetype,
est.dent, localsettings.operator, est.ix)
estimate_updates.append((ESTS_UPDATE_QUERY, values, est))
for tx_hash in est.tx_hashes:
values = (pt.serialno, tx_hash)
post_cleanup_commands.append(
(update_daybook_after_estimate_change, values))
old_ests_dict.pop(est.ix)
#-- all that is left in old_ests_dict now are items which
#-- have been removed.
#-- so remove from database if they are current course!
for ix, old_est in old_ests_dict.iteritems():
#--removed
if old_est.courseno == pt.courseno0:
values = (ix,)
estimate_deletions.append((EST_DEL_QUERY, values))
estimate_deletions.append((EST_LINK_DEL_QUERY, values))
for tx_hash in old_est.tx_hashes:
values = (pt.serialno, tx_hash)
post_cleanup_commands.append(
(update_daybook_after_estimate_change, values))
db = connect.connect()
cursor = db.cursor()
for query, values, tx_hashes in estimate_insertions:
LOGGER.debug(query)
LOGGER.debug(values)
cursor.execute(query, values)
ix = cursor.lastrowid
for tx_hash in tx_hashes:
vals = (ix, tx_hash.hash, tx_hash.completed)
cursor.execute(EST_LINK_INS_QUERY, vals)
for query, values, estimate in estimate_updates:
LOGGER.debug(query)
LOGGER.debug(values)
cursor.execute(query, values)
cursor.execute(EST_LINK_DEL_QUERY, (estimate.ix,))
for tx_hash in estimate.tx_hashes:
cursor.execute(EST_LINK_INS_QUERY,
(estimate.ix, tx_hash.hash,
tx_hash.completed)
)
for query, values in estimate_deletions:
LOGGER.debug(query)
LOGGER.debug(values)
cursor.execute(query, values)
cursor.close()
for func, values in post_cleanup_commands:
func.__call__(values)
return result
if __name__ == "__main__":
ests = get_ests(11956, 29749)
print ests
print "equality test (should be True) ", ests[0] == ests[0]
print "inequality test (should also be True)", ests[0] != ests[1]
openmolar-0.6.2/src/openmolar/dbtools/estimatesHistory.py 0000644 0001750 0001750 00000010527 12350030721 023574 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from openmolar.settings import localsettings
from openmolar.connect import connect
from openmolar.ptModules.estimates import Estimate, TXHash
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
QUERY = '''SELECT newestimates.ix, number, itemcode, description,
fee, ptfee, feescale, csetype, dent, est_link2.completed, tx_hash, courseno
from newestimates right join est_link2 on newestimates.ix = est_link2.est_id
where serialno=%s order by courseno desc, itemcode, ix'''
COURSE_QUERY = QUERY.replace(
"order by courseno desc,", "and courseno = %s order by")
ALLOW_EDIT = False
EDIT_STRING = '%s' % _("Edit this Estimate")
def getEsts(sno, courseno=None):
db = connect()
cursor = db.cursor()
if courseno is None:
cursor.execute(QUERY, (sno,))
else:
cursor.execute(COURSE_QUERY, (sno, courseno))
rows = cursor.fetchall()
cursor.close()
estimates = OrderedDict()
for row in rows:
hash_ = row[10]
completed = bool(row[9])
tx_hash = TXHash(hash_, completed)
ix = row[0]
est = estimates.get(ix, Estimate())
est.ix = ix
est.courseno = row[11]
est.number = row[1]
est.itemcode = row[2]
est.description = row[3]
est.fee = row[4]
est.ptfee = row[5]
est.feescale = row[6]
est.csetype = row[7]
est.dent = row[8]
try:
est.tx_hashes.append(tx_hash)
except AttributeError:
est.tx_hashes = [tx_hash]
estimates[ix] = est
return estimates.values()
def details(sno):
'''
returns an html page showing pt's old estimates
'''
estimates = getEsts(sno)
claimNo = len(estimates)
html = "%s - %d %s" % (
_("Past Estimates"),
claimNo,
_("found")
)
if claimNo == 0:
return html
courseno = None
for i, est in enumerate(estimates):
if est.courseno != courseno:
header = est.htmlHeader()
if ALLOW_EDIT:
header = header.replace(
"",
EDIT_STRING % est.courseno
)
if i > 0:
html += " "
html += '%s' % header
courseno = est.courseno
html += est.toHtmlRow()
html += ' \n'
return html
if __name__ == "__main__":
localsettings.initiate()
print''
print details(707).encode("ascii", "replace")
print ""
openmolar-0.6.2/src/openmolar/dbtools/families.py 0000644 0001750 0001750 00000012303 12353117723 022012 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from openmolar.connect import connect
from openmolar.settings import localsettings
QUERY = '''select serialno, title, fname, sname,
addr1, addr2, addr3, town, county, pcde, dob, status, tel1 from new_patients
where familyno = %s order by dob'''
PATIENT_QUERY = QUERY.replace("familyno", "serialno")
LINK_QUERY = 'update new_patients set familyno=%s where serialno=%s'
SYNC_QUERY = '''update new_patients set
addr1=%s, addr2=%s, addr3=%s, town=%s, county=%s, pcde=%s
where familyno=%s'''
NEXT_FAMILYNO_QUERY = "select max(familyno)+1 from new_patients"
NEW_GROUP_QUERY = "update new_patients set familyno=%s where serialno=%s"
DELETE_FAMILYNO_QUERY = \
"update new_patients set familyno=NULL where familyno=%s"
ADDRESS_MATCH_QUERY = '''select
case when addr1 = %s then 4 else 0 end +
case when addr1 like %s then 3 else 0 end +
case when addr2 like %s then 3 else 0 end +
case when addr3 like %s then 1 else 0 end +
case when town like %s then 1 else 0 end +
case when pcde = %s then 5 else 0 end as matches ,
serialno, title, fname, sname, dob, addr1, addr2, addr3, town, pcde
from new_patients
where
addr1 like %s or
(addr2 != "" and addr2 like %s) or
(town != "" and town like %s) or
(pcde=%s and pcde != "")
order by matches desc
limit 12
'''
def new_group(serialno):
'''
start a new family with one member - serialno
'''
db = connect()
cursor = db.cursor()
cursor.execute(NEXT_FAMILYNO_QUERY)
family_no = cursor.fetchone()[0]
if family_no is None:
family_no = 1
cursor.execute(NEW_GROUP_QUERY, (family_no, serialno))
cursor.close()
return family_no
def delete_group(family_no):
'''
delete all reference to familyno for all records
'''
db = connect()
cursor = db.cursor()
cursor.execute(DELETE_FAMILYNO_QUERY, (family_no,))
cursor.close()
def add_member(family_no, serialno):
'''
add serialno to group familyno
'''
db = connect()
cursor = db.cursor()
cursor.execute(LINK_QUERY, (family_no, serialno))
cursor.close()
def remove_member(serialno):
'''
remove any family reference for record serialno
'''
add_member(None, serialno)
def get_members(family_no):
'''
get members of the family with number familyno
'''
db = connect()
cursor = db.cursor()
cursor.execute(QUERY, (family_no,))
members = cursor.fetchall()
cursor.close()
return members
def sync_addresses(family_no, chosen_address):
'''
set all familyno addresses to this address
returns the number of records changed.
'''
db = connect()
cursor = db.cursor()
values = tuple(chosen_address) + (family_no,)
count = cursor.execute(SYNC_QUERY, values)
cursor.close()
return count
def get_patient_details(serialno):
db = connect()
cursor = db.cursor()
cursor.execute(PATIENT_QUERY, (serialno,))
member = cursor.fetchone()
cursor.close()
return member
def get_address_matches(address):
'''
find possible address matches for the address used.
'''
addr1 = address[0]
addr2 = address[1]
addr3 = address[2]
town = address[3]
county = address[4]
pcde = address[5]
db = connect()
cursor = db.cursor()
values = (
addr1,
addr1[:10],
addr2[:10],
addr3[:10],
town[:10],
pcde,
addr1[:10],
addr2[:10],
town[:10],
pcde[:10],
)
cursor.execute(ADDRESS_MATCH_QUERY, (values))
rows = cursor.fetchall()
cursor.close()
return rows
if __name__ == "__main__":
print new_group(1)
openmolar-0.6.2/src/openmolar/dbtools/feescales.py 0000644 0001750 0001750 00000022316 12350030721 022145 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
import os
import re
import shutil
from collections import namedtuple
from openmolar import connect
from openmolar.settings import localsettings
LOGGER = logging.getLogger("openmolar")
def FEESCALE_DIR():
'''
this is dynamic in case user switches database
'''
return os.path.join(
localsettings.localFileDirectory,
"feescales",
connect.params.database_name.replace(" ", "_").replace(":", "_PORT_")
)
def write_readme():
dir_path = FEESCALE_DIR()
LOGGER.info("creating directory %s" % dir_path)
os.makedirs(dir_path)
f = open(os.path.join(dir_path, "README.txt"), "w")
f.write('''
This folder is created by openmolar to store xml copies of the feescales in
database %s.
Filenames herein are IMPORTANT!
feescale1.xml relates to the xml stored in row 1 of that table
feescale2.xml relates to the xml stored in row 2 of that table
whilst you are free to edit these files using an editor of your choice,
validation against feescale_schema.xsd is highly recommended.
note - openmolar has a build in application for doing this.
in addition - why not use some version control for this folder?
''' % connect.params.database_name)
f.close()
QUERY = 'select ix, xml_data from feescales'
SPECIFIC_QUERY = 'select xml_data from feescales where ix=%s'
UPDATE_QUERY = "update feescales set xml_data = %s where ix = %s"
NEW_FEESCALE_QUERY = "insert into feescales (xml_data) values(%s)"
def get_digits(string_value):
'''
used as a key for sort function for filenames.
I want foo_10 to be after foo_9 etc..
'''
m = re.search("(\d+)", string_value)
if not m:
return None
return int(m.groups()[0])
class FeescaleHandler(object):
ixs_in_db = set([])
def get_feescale_from_database(self, ix):
'''
connects and gets the xml_data associated with ix
'''
db = connect.connect()
cursor = db.cursor()
cursor.execute(SPECIFIC_QUERY, (ix,))
row = cursor.fetchone()
cursor.close()
if row:
return row[0]
return ""
def get_feescales_from_database(self,
in_use_only=True, priority_order=True):
'''
connects and get the data from feetable_key
'''
query = QUERY
if in_use_only:
query += ' where in_use = True'
else: # if called by feescale editor
self.ixs_in_db = set([])
if priority_order:
query += ' order by priority desc'
db = connect.connect()
cursor = db.cursor()
cursor.execute(query)
rows = cursor.fetchall()
cursor.close()
LOGGER.debug("%d feescales retrieved" % len(rows))
for ix, xml_data in rows:
self.ixs_in_db.add(ix)
return rows
def save_file(self, ix, xml_data):
file_path = self.index_to_local_filepath(ix)
LOGGER.debug("writing %s" % file_path)
f = open(file_path, "w")
f.write(xml_data)
f.close()
def _xml_data_and_filepaths(self):
for ix, xml_data in self.get_feescales_from_database(False):
xml_file = namedtuple("XmlFile", ("data", "filepath"))
xml_file.data = xml_data
xml_file.filepath = self.index_to_local_filepath(ix)
yield xml_file
def non_existant_and_modified_local_files(self):
'''
returns 2 lists
[local files which have been created]
[local files which differ from stored data]
'''
unwritten, modified = [], []
for xml_file in self._xml_data_and_filepaths():
if not os.path.isfile(xml_file.filepath):
unwritten.append(xml_file)
else:
f = open(xml_file.filepath, "r")
if f.read().strip() != xml_file.data.strip():
modified.append(xml_file)
f.close()
return unwritten, modified
def index_to_local_filepath(self, ix):
return os.path.join(FEESCALE_DIR(), "feescale_%d.xml" % ix)
def check_dir(self):
if not os.path.exists(FEESCALE_DIR()):
write_readme()
@property
def local_files(self):
self.check_dir()
dirname = FEESCALE_DIR()
for file_ in sorted(os.listdir(dirname), key=get_digits):
m = re.match("feescale_(\d+)\.xml$", file_)
if m:
ix = int(m.groups()[0])
yield ix, os.path.join(dirname, file_)
def temp_move(self, file_ix):
'''
after insert, a local file may need to move.
this is done cautiously as could overwrite another
'''
path = self.index_to_local_filepath(file_ix)
shutil.move(path, path + "temp")
def final_move(self, file_ix, db_ix):
'''
finalised temp_move
'''
temp_path = self.index_to_local_filepath(file_ix) + "temp"
final_path = self.index_to_local_filepath(db_ix)
shutil.move(temp_path, final_path)
def update_db_all(self):
'''
apply all local file changes to the database.
'''
message = ""
insert_ids = []
for ix, filepath in self.local_files:
if ix in self.ixs_in_db:
message += self.update_db(ix)
else:
insert_ids.append(ix)
return message, insert_ids
def update_db(self, ix):
message = ""
filepath = self.index_to_local_filepath(ix)
LOGGER.debug("updating database ix %s" % ix)
if not os.path.isfile(filepath):
message = "FATAL %s does not exist!" % filepath
else:
db = connect.connect()
cursor = db.cursor()
f = open(filepath)
data = f.read()
f.close()
values = (data, ix)
result = cursor.execute(UPDATE_QUERY, values)
r_message = "commiting feescale '%s' to database." % filepath
message = "updating feescale %d result = %s\n" % (
ix, "OK" if result else "No Change applied")
db.close()
LOGGER.info(r_message + " " + message)
return message
def insert_db(self, ix):
message = ""
filepath = self.index_to_local_filepath(ix)
LOGGER.debug("inserting new feescale into database %s" % ix)
if not os.path.isfile(filepath):
message = "FATAL %s does not exist!" % filepath
else:
db = connect.connect()
cursor = db.cursor()
f = open(filepath)
data = f.read()
f.close()
values = (data,)
cursor.execute(NEW_FEESCALE_QUERY, values)
db_ix = db.insert_id()
self.ixs_in_db.add(db_ix)
r_message = "inserting new feescale '%s' to database." % filepath
db.close()
LOGGER.info(r_message)
return db_ix
def save_xml(self, ix, xml):
file_path = self.index_to_local_filepath(ix)
LOGGER.info("saving %s" % file_path)
LOGGER.debug("creating backup")
try:
shutil.copy(file_path, file_path + "~")
except IOError:
LOGGER.warning("no backup file created")
f = open(file_path, "w")
f.write(xml)
f.close()
return True
feescale_handler = FeescaleHandler()
if __name__ == "__main__":
logging.basicConfig()
LOGGER.setLevel(logging.DEBUG)
fh = FeescaleHandler()
fh.get_feescales_from_database()
for ix, local_file in fh.local_files:
print ix, local_file
print fh.non_existant_and_modified_local_files()
openmolar-0.6.2/src/openmolar/dbtools/forum.py 0000644 0001750 0001750 00000012544 12375601671 021364 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import sys
from openmolar import connect
from openmolar.settings import localsettings
headers = [_("Subject"), "db_index", _("From"), _("To"),
_("Date"), _("Message"), _("Message")] # , "parent"]
HIGHESTID = 0
class post():
def __init__(self):
self.ix = None
self.parent_ix = None
self.inits = ""
self.recipient = None
self.date = None
self.topic = ""
self.comment = ""
self.briefcomment = ""
self.open = True
def commitPost(post):
# use a different connection for forum, as it runs in a separate thread
db = connect.connect()
cursor = db.cursor()
columns = "parent_ix,inits,recipient,fdate,topic,comment"
values = (post.parent_ix, post.inits, post.recipient,
post.topic, post.comment.replace("\n", " "))
query = \
"insert into forum (%s) VALUES (%%s,%%s,%%s,NOW(),%%s,%%s)" % columns
cursor.execute(query, values)
db.commit()
def deletePost(ix):
db = connect.connect()
cursor = db.cursor()
query = "update forum set open=False where ix=%s"
cursor.execute(query, (ix,))
db.commit()
cursor.close()
def setParent(ix, parent_ix):
db = connect.connect()
cursor = db.cursor()
query = "update forum set parent_ix=%s where ix=%s"
cursor.execute(query, (parent_ix, ix))
db.commit()
cursor.close()
def newPosts():
result = False
try:
users = localsettings.operator.split("/")
if users == []:
return
db = connect.connect()
cursor = db.cursor()
query = '''select max(ix) from forum'''
cursor.execute(query)
row = cursor.fetchone()
query = "select max(id) from forumread where"
for user in users:
query += " op='%s' or" % user
cursor.execute(query.strip("or"))
row2 = cursor.fetchone()
cursor.close()
result = row[0] > row2[0]
except connect.ProgrammingError as e:
print e
return result
def updateReadHistory():
users = localsettings.operator.split("/")
# print "updating forumread for new posts for ", users
if users == []:
return
db = connect.connect()
cursor = db.cursor()
query = "insert into forumread set id=%s, op=%s, readdate=NOW()"
for user in users:
values = (HIGHESTID, user)
cursor.execute(query, values)
cursor.close()
def getPosts(user=None, include_closed=False):
'''
gets all active rows from a forum table
'''
global HIGHESTID
conditions, values = ["open"], [not include_closed]
if user:
conditions.append('recipient')
values.append(user)
db = connect.connect()
cursor = db.cursor()
query = ('SELECT ix, parent_ix, topic, inits, fdate, recipient, comment '
'FROM forum where %s ORDER BY parent_ix, ix' %
" and ".join(["%s=%%s"%val for val in conditions]))
cursor.execute(query, values)
rows = cursor.fetchall()
cursor.close()
retarg = []
update = False
for row in rows:
newpost = post()
newpost.ix = row[0]
if newpost.ix > HIGHESTID:
HIGHESTID = newpost.ix
update = True
newpost.parent_ix = row[1]
newpost.topic = row[2]
newpost.inits = row[3]
newpost.date = row[4]
newpost.recipient = row[5]
newpost.comment = row[6]
newpost.briefcomment = row[6][:40]
if newpost.comment != newpost.briefcomment:
newpost.briefcomment += "...."
retarg.append(newpost)
if update:
updateReadHistory()
return retarg
if __name__ == "__main__":
posts = getPosts(user="NW")
for post in posts:
print post.parent_ix, post.ix, post.topic
openmolar-0.6.2/src/openmolar/dbtools/medhist.py 0000644 0001750 0001750 00000017016 12401724313 021655 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
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)
openmolar-0.6.2/src/openmolar/dbtools/memos.py 0000644 0001750 0001750 00000012136 12320217277 021344 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from openmolar.connect import connect
from openmolar.settings import localsettings
QUERY = '''SELECT ix, serialno, author, type, mdate, message FROM ptmemos
WHERE serialno = %s and open = 1 AND
(expiredate is NULL or expiredate >= curdate()) AND (type = %s OR type = "all")
order by ix'''
QUERY_ALL = '''SELECT ix, serialno, author, type, mdate, message,
open, expiredate FROM ptmemos WHERE serialno = %s order by ix'''
INSERT_QUERY = '''INSERT into ptmemos
(serialno, author, type, mdate, expiredate, message, open)
VALUES (%s, %s, %s, NOW(), %s, %s, %s)'''
DELETE_QUERY = "update ptmemos set open = 0 where ix=%s"
class Memo(object):
def __init__(self):
self.ix = None
self.serialno = 0
self.author = ""
self.type = ""
self.mdate = None
self.expire = None
self.message = None
self.open = False
def setMessage(self, arg):
self.message = arg
def get_memos(serialno):
db = connect()
if localsettings.station == "surgery":
values = (serialno, "surg")
elif localsettings.station == "reception":
values = (serialno, "rec")
else:
values = (serialno, "all")
cursor = db.cursor()
cursor.execute(QUERY, values)
rows = cursor.fetchall()
cursor.close()
for row in rows:
memo = Memo()
memo.ix = row[0]
memo.serialno = row[1]
memo.author = row[2]
memo.type = row[3]
memo.mdate = row[4]
memo.setMessage(row[5])
memo.open = True
yield memo
def deleteMemo(ix):
db = connect()
cursor = db.cursor()
cursor.execute(DELETE_QUERY, (ix,))
cursor.close()
db.commit()
def saveMemo(serialno, author, type, expire, message, open):
'''
put a memo into the database
'''
db = connect()
values = (serialno, author, type, expire, message, open)
cursor = db.cursor()
result = cursor.execute(INSERT_QUERY, values)
db.commit()
cursor.close()
return result
def html_history(serialno):
db = connect()
cursor = db.cursor()
cursor.execute(QUERY_ALL, (serialno,))
rows = cursor.fetchall()
cursor.close()
if not rows:
return u'''
%s
''' % _("No memo history found")
html = u'''
%s
''' % _("Memo History")
html += u'''
%s |
%s |
%s |
%s |
%s |
%s |
''' % (
_("Author"),
_("Location"),
_("Date"),
_("Expires"),
_("Deleted?"),
_("Message"))
for row in rows:
ix = row[0]
serialno = row[1]
author = row[2]
type = row[3]
mdate = row[4]
message = row[5]
open_ = row[6]
expiry_date = row[7]
html += u'''
%s |
%s |
%s |
%s |
%s |
%s |
''' % (
author,
type,
localsettings.formatDate(mdate),
localsettings.formatDate(expiry_date),
_("Yes") if not open_ else _("No"),
message)
return html + " "
if __name__ == "__main__":
print html_history(11956)
openmolar-0.6.2/src/openmolar/dbtools/nhs_claims.py 0000644 0001750 0001750 00000006542 12320217277 022350 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''
module for getting/setting information from the claims table
'''
from openmolar.connect import connect
def details(sno):
'''returns an html set showing pt name etc...'''
headers = 'courseno,serialno,dntix,proddate,startdate,cmpldate,regdate,'
headers += 'authdate,dob,sname,fname,addr1,addr2,addr3,pcde,nhsno,'
headers += 'prevsname,exempttext,i0,i1,i2,i3,i4,f0,f1,f2,f3,f4,f5,f6,f7,'
headers += 'f8,f9,submstatus,submcount,submno,archdate,'
headers += 'town,county,regtype' # claimdata,trtdata,
db = connect()
cursor = db.cursor()
cursor.execute(
'select %s from claims where serialno=%d order by proddate DESC' % (
headers, sno))
rows = cursor.fetchall()
cursor.close()
claimNo = len(rows)
retarg = "NHS Claims - %d found" % claimNo
if claimNo == 0:
return retarg
retarg += ''
retarg += '- | '
for i2 in range(len(rows)):
bgcolor = ""
if i2 % 2 == 0:
bgcolor = ' bgcolor="#eeffff"'
retarg += 'Claim %s | ' % (bgcolor, i2 + 1)
retarg += ' '
headerArray = headers.split(",")
for i in range(len(headerArray)):
retarg += ""
retarg += "%s | " % headerArray[i]
for i2 in range(len(rows)):
bgcolor = ""
if i2 % 2 == 0:
bgcolor = ' bgcolor="#eeffff"'
val = rows[i2][i]
if not val:
val = "-"
retarg += '%s | ' % (bgcolor, val)
retarg += ' \n'
retarg += ' '
# db.close()
return retarg
if __name__ == "__main__":
print ''
print details(17322)
print ""
openmolar-0.6.2/src/openmolar/dbtools/patient_class.py 0000644 0001750 0001750 00000071202 12455453236 023062 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from copy import deepcopy
import datetime
import logging
import re
import sys
from openmolar import connect
from openmolar.ptModules import dec_perm, formatted_notes
from openmolar.settings import localsettings
from openmolar.dbtools.appt_prefs import ApptPrefs
from openmolar.dbtools.treatment_course import TreatmentCourse
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, QUICK_MED_QUERY)
LOGGER = logging.getLogger("openmolar")
dateFields = ("dob", "pd0", "pd1", "pd2", "pd3", "pd4", "pd5", "pd6",
"pd7", "pd8", "pd9", "pd10", "pd11", "pd12", "pd13", "pd14", "cnfd",
"recd", "billdate", "enrolled", "initaccept", "lastreaccept", "lastclaim",
"expiry", "transfer", "chartdate", "accd", "cmpd", "examd", "bpedate")
nullDate = None
patientTableAtts = (
'sname', 'fname', 'title', 'sex', 'dob',
'addr1', 'addr2', 'addr3', 'pcde', 'town', 'county',
'tel1', 'tel2', 'mobile', 'fax', 'email1', 'email2',
'occup', 'nhsno', 'cnfd', 'cset', 'dnt1', 'dnt2', 'courseno0',
'billdate', 'billct', 'billtype', 'familyno', 'memo', 'status'
)
money_table_atts = ('money0', 'money1', 'money2', 'money3', 'money4',
'money5', 'money6', 'money7', 'money8', 'money9', 'money10', 'money11')
nhs_table_atts = ('initaccept', 'lastreaccept', 'lastclaim', 'expiry',
'cstatus', 'transfer', 'pstatus')
static_table_atts = (
'ur8st', 'ur7st', 'ur6st', 'ur5st', 'ur4st', 'ur3st', 'ur2st', 'ur1st',
'ul1st', 'ul2st', 'ul3st', 'ul4st', 'ul5st', 'ul6st', 'ul7st', 'ul8st',
'll8st', 'll7st', 'll6st', 'll5st', 'll4st', 'll3st', 'll2st', 'll1st',
'lr1st', 'lr2st', 'lr3st', 'lr4st', 'lr5st', 'lr6st', 'lr7st', 'lr8st',
'dent0', 'dent1', 'dent2', 'dent3')
date_table_atts = (
'pd0', 'pd1', 'pd2', 'pd3', 'pd4', 'pd5', 'pd6', 'pd7', 'pd8', 'pd9',
'pd10', 'pd11', 'pd12', 'pd13', 'pd14')
exemptionTableAtts = ('exemption', 'exempttext')
bpeTableAtts = ('bpedate', 'bpe')
bpeTableVals = (nullDate, '', ())
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',
'll1', 'll2', 'll3', 'll4', 'll5', 'll6', 'll7', 'll8']
decidmouth = ['***', '***', '***', 'ulE', 'ulD', 'ulC', 'ulB', 'ulA',
'urA', 'urB', 'urC', 'urD', 'urE', '***', '***', '***',
'***', '***', '***', 'lrE', 'lrD', 'lrC', 'lrB', 'lrA',
'llA', 'llB', 'llC', 'llD', 'llE', '***', '***', '***']
clinical_memos = ("synopsis",)
_atts = []
for att in PATIENT_QUERY_FIELDS:
if re.match("[ul][lr]\d$", att):
_atts.append(att + "st")
else:
_atts.append(att)
patient_query_atts = tuple(_atts)
class patient(object):
def __init__(self, sno):
'''
initiate the class with default variables, then load from database
'''
self.serialno = sno
self.dbstate = None
self.load_warnings = []
# patient table atts
self.courseno0 = None
self.money0 = 0
self.money1 = 0
self.money2 = 0
self.money3 = 0
self.money4 = 0
self.money5 = 0
self.money6 = 0
self.money7 = 0
self.money8 = 0
self.money9 = 0
self.money10 = 0
self.pd0 = None
self.pd1 = None
self.pd2 = None
self.pd3 = None
self.pd4 = None # this field is no longer used (last treatment date)
self.pd5 = None
self.pd6 = None
self.pd7 = None
self.pd8 = None
self.pd9 = None
self.pd10 = None
self.pd11 = None
self.pd12 = None
self.pd13 = None
self.pd14 = None
self.sname = ''
self.fname = ''
self.title = ''
self.sex = ''
self.dob = None
self.addr1 = ''
self.addr2 = ''
self.addr3 = ''
self.pcde = ''
self.tel1 = ''
self.tel2 = ''
self.occup = ''
self.nhsno = ''
self.cnfd = None
self.cset = ''
self.dnt1 = 0
self.dnt2 = 0
self.ur8st = ''
self.ur7st = ''
self.ur6st = ''
self.ur5st = ''
self.ur4st = ''
self.ur3st = ''
self.ur2st = ''
self.ur1st = ''
self.ul1st = ''
self.ul2st = ''
self.ul3st = ''
self.ul4st = ''
self.ul5st = ''
self.ul6st = ''
self.ul7st = ''
self.ul8st = ''
self.ll8st = ''
self.ll7st = ''
self.ll6st = ''
self.ll5st = ''
self.ll4st = ''
self.ll3st = ''
self.ll2st = ''
self.ll1st = ''
self.lr1st = ''
self.lr2st = ''
self.lr3st = ''
self.lr4st = ''
self.lr5st = ''
self.lr6st = ''
self.lr7st = ''
self.lr8st = ''
self.dent0 = 0
self.dent1 = 0
self.dent2 = 0
self.dent3 = 0
self.billdate = None
self.billct = 0
self.billtype = None
self.money11 = 0
self.familyno = localsettings.last_family_no
self.memo = ''
self.town = ''
self.county = ''
self.mobile = ''
self.fax = ''
self.email1 = ''
self.email2 = ''
self.status = ''
self.initaccept = 0
self.lastreaccept = None
self.lastclaim = None
self.expiry = None
self.cstatus = None
self.transfer = 0
self.pstatus = None
self.estimates = []
# from userdata
self.plandata = PlanData(self.serialno)
# NEIL'S STUFF####
self.exemption = ""
self.exempttext = ""
self.bpe = []
self.bpedate = nullDate
self.chartdate = nullDate
self.notes_dict = {}
self.MEDALERT = False
self.mh_chkdate = None
self.HIDDENNOTES = []
self.chartgrid = {}
self._fee_table = None
self.synopsis = ""
self._n_family_members = None
self._dayBookHistory = None
self.treatment_course = None
self.est_logger = None
self._most_recent_daybook_entry = None
self._has_exam_booked = None
self._previous_surnames = None
self.monies_reset = False
if self.serialno == 0:
return
#
# now load stuff from the database ##
#
db = connect.connect()
cursor = db.cursor()
self.getSynopsis()
cursor.execute(PATIENT_QUERY, (self.serialno,))
values = cursor.fetchall()
if values == ():
raise localsettings.PatientNotFoundError
for i, att in enumerate(patient_query_atts):
value = values[0][i]
if value is not None:
self.__dict__[att] = value
elif att == "familyno":
self.familyno = 0
query = '''select exemption, exempttext from exemptions
where serialno=%s'''
cursor.execute(query, self.serialno)
values = cursor.fetchall()
for value in values:
self.exemption, self.exempttext = value
query = '''select bpedate, bpe from bpe where serialno=%s
order by bpedate'''
cursor.execute(query, self.serialno)
values = cursor.fetchall()
for value in values:
self.bpe.append(value)
if self.courseno0 != 0:
self.getEsts()
self.treatment_course = TreatmentCourse(
self.serialno, self.courseno0)
self.getNotesTuple()
cursor.execute(QUICK_MED_QUERY, (self.serialno,))
try:
self.MEDALERT, self.mh_chkdate = cursor.fetchone()
except TypeError:
pass
cursor.close()
# db.close()
#-- load from plandata
self.plandata.getFromDB()
self.appt_prefs = ApptPrefs(self.serialno)
self.updateChartgrid()
self.take_snapshot()
@property
def appt_memo(self):
return self.appt_prefs.note
def set_appt_memo(self, memo):
self.appt_prefs.note = memo
@property
def recall_active(self):
return self.appt_prefs.recall_active
@property
def recd(self):
return self.appt_prefs.recdent
@property
def dayBookHistory(self):
if self._dayBookHistory is None:
db = connect.connect()
cursor = db.cursor()
query = 'select date, trtid, chart from daybook where serialno=%s'
cursor.execute(query, self.serialno)
self._dayBookHistory = cursor.fetchall()
cursor.close()
return self._dayBookHistory
@property
def last_treatment_date(self):
max_date = localsettings.currentDay()
if (self.treatment_course.cmp_txs !=
self.dbstate.treatment_course.cmp_txs):
return max_date
if self._most_recent_daybook_entry is None:
db = connect.connect()
cursor = db.cursor()
query = 'select max(date) from daybook where serialno=%s'
if cursor.execute(query, self.serialno):
max_date = cursor.fetchone()[0]
cursor.close()
self._most_recent_daybook_entry = max_date
return self._most_recent_daybook_entry
def forget_exam_booked(self):
self._has_exam_booked = None
@property
def has_exam_booked(self):
if self._has_exam_booked is None:
db = connect.connect()
cursor = db.cursor()
cursor.execute(FUTURE_EXAM_QUERY, self.serialno)
self._has_exam_booked = bool(cursor.fetchone()[0])
cursor.close()
return self._has_exam_booked
def __repr__(self):
return "'Patient_class instance - serialno %d'" % self.serialno
@property
def address(self):
'''
a printable address
'''
address = ""
for line in (self.addr1, self.addr2, self.addr3,
self.town, self.county, self.pcde):
if line.strip(" ") != "":
address += "%s\n" % line.strip(" ")
return address
def getAge(self, on_date=None):
'''
return the age in form (year(int), months(int), isToday(bool))
'''
if on_date is None:
# use today
on_date = localsettings.currentDay()
day = self.dob.day
try:
nextbirthday = datetime.date(on_date.year, self.dob.month,
self.dob.day)
except ValueError:
# catch leap years!!
nextbirthday = datetime.date(on_date.year, self.dob.month,
self.dob.day - 1)
ageYears = on_date.year - self.dob.year
if nextbirthday > on_date:
ageYears -= 1
months = (12 - self.dob.month) + on_date.month
else:
months = on_date.month - self.dob.month
if self.dob.day > on_date.day:
months -= 1
isToday = nextbirthday == localsettings.currentDay()
return (ageYears, months, isToday)
@property
def ageYears(self):
return self.getAge()[0]
@property
def age_course_start(self):
'''
returns a tuple (year, months) for the patient at accd
'''
return self.getAge(self.treatment_course.accd)[:2]
@property
def under_6(self):
'''
returns a bool "is patient under 6?".
'''
return self.ageYears < 6
@property
def under_18(self):
'''
returns a bool "is patient under 18?".
'''
return self.ageYears < 18
def forget_fee_table(self):
self._fee_table = None
@property
def fee_table(self):
'''
logic to determine which feeTable should be used for standard items
'''
if self._fee_table is None:
if self.treatment_course.accd is None:
cse_accd = localsettings.currentDay()
else:
cse_accd = self.treatment_course.accd
for table in reversed(localsettings.FEETABLES.tables.values()):
LOGGER.debug(
"checking feescale %s to see if suitable a feetable" % (
table))
start, end = table.startDate, table.endDate
LOGGER.debug("categories, start, end = %s, %s, %s" % (
table.categories, start, end))
if end is None:
end = localsettings.currentDay()
if self.cset in table.categories and start <= cse_accd <= end:
self._fee_table = table
if self._fee_table is None:
#-- no matching table found, use the default.
LOGGER.warning("NO SUITABLE FEETABLE FOUND, RETURNING DEFAULT")
self._fee_table = localsettings.FEETABLES.default_table
return self._fee_table
def getEsts(self):
'''
get estimate data
'''
self.estimates = db_estimates.get_ests(self.serialno, self.courseno0)
self.est_logger = EstLogger(self.courseno0)
def getSynopsis(self):
db = connect.connect()
cursor = db.cursor()
fields = clinical_memos
query = ""
for field in fields:
query += field + ","
query = query.strip(",")
try:
if cursor.execute(
'SELECT %s from clinical_memos where serialno=%d' % (query,
self.serialno)):
self.synopsis = cursor.fetchall()[-1][0]
except connect.OperationalError as e:
'necessary because the column is missing is db schema 1.4'
print "WARNING -", e
@property
def underTreatment(self):
return (self.treatment_course is not None and
self.treatment_course.underTreatment)
@property
def max_tx_courseno(self):
return self.treatment_course.max_tx_courseno
@property
def newer_course_found(self):
return self.treatment_course.newer_course_found
def getNotesTuple(self):
'''
connect and poll the formatted_notes table
'''
self.notes_dict = formatted_notes.get_notes_dict(self.serialno)
def flipDec_Perm(self, tooth):
'''
switches a deciduous tooth to a permanent one,
and viceVersa pass a variable like "ur5"
'''
quadrant = tooth[:2]
pos = int(tooth[2]) - 1 # will be 0-7
if quadrant == "ul":
var = self.dent1
pos = 7 - pos
elif quadrant == "ur":
var = self.dent0
elif quadrant == "ll":
var = self.dent2
else: # lr
var = self.dent3
pos = 7 - pos
existing = dec_perm.fromSignedByte(var)
if existing[pos] == "1":
existing = existing[:pos] + "0" + existing[pos + 1:]
else:
existing = existing[:pos] + "1" + existing[pos + 1:]
if quadrant == "ul":
self.dent1 = dec_perm.toSignedByte(existing)
elif quadrant == "ur":
self.dent0 = dec_perm.toSignedByte(existing)
elif quadrant == "ll":
self.dent2 = dec_perm.toSignedByte(existing)
else: # lr
self.dent3 = dec_perm.toSignedByte(existing)
self.updateChartgrid()
def updateChartgrid(self):
grid = ""
for quad in (self.dent1, self.dent0, self.dent3, self.dent2):
grid += dec_perm.fromSignedByte(quad)
for pos in mouth:
if grid[mouth.index(pos)] == "0":
self.chartgrid[pos] = pos
else:
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:
self.money1 = self.dbstate.money1 + self.fees_accrued
@property
def fees(self):
return int(self.money0 + self.money1 + self.money9 + self.money10 +
self.money11 - self.money2 - self.money3 - self.money8)
@property
def fees_accrued(self):
old_estimate_charges = 0
if self.courseno0 == self.dbstate.courseno0:
old_estimate_charges = self.dbstate.estimate_charges
accrued_fees = self.estimate_charges - old_estimate_charges
LOGGER.debug("fees_accrued = (new-existing) = %d - %d = %d" % (
self.estimate_charges,
old_estimate_charges,
accrued_fees)
)
return accrued_fees
@property
def estimate_charges(self):
charges = 0
for est in self.estimates:
if est.completed == 2:
charges += est.ptfee
elif est.completed == 1:
charges += est.interim_pt_fee
return charges
@property
def est_logger_text(self):
'''
a summary of the estimate for use in the est_logger_table
est_logger is unconcerned whether treatment is completed etc..
'''
text = ""
total, p_total = 0, 0
for estimate in sorted(self.estimates):
text += estimate.log_text
total += estimate.fee
p_total += estimate.ptfee
text += "TOTAL || || || || || || %s || %s" % (total, p_total)
return text
def resetAllMonies(self):
'''
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 = self.money0
self.money3 = self.money1
self.money8 = 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,
one_only=False):
'''
re-written for schema 1.9
'''
LOGGER.info("(ntype='%s',note='%s', attempt_delete='%s'",
ntype, note, attempt_delete)
HN = ()
if ntype == "payment":
HN = ("RECEIVED: ", note)
elif ntype == "printed":
HN = ("PRINTED: ", note)
elif ntype == "exam":
HN = ("TC: EXAM", note)
elif ntype == "chart_treatment":
HN = ("TC:", note)
elif ntype == "perio_treatment":
HN = ("TC: PERIO", note)
elif ntype == "xray_treatment":
HN = ("TC: XRAY", note)
elif ntype == "treatment":
HN = ("TC: OTHER", note)
elif ntype == "mednotes": # other treatment
HN = ("UPDATED:Medical Notes", note)
elif ntype == "close_course":
HN = ("COURSE CLOSED", "=" * 10)
elif ntype == "open_course":
HN = ("COURSE OPENED", "= " * 5)
elif ntype == "resume_course":
HN = ("COURSE RE-OPENED", "= " * 5)
elif ntype == "fee":
HN = ("INTERIM: ", note)
if not HN:
LOGGER.warning(
"unable to add Hidden Note notetype '%s' not found", ntype)
return
reversing_note = ("UNCOMPLETED", "{%s}" % note)
if attempt_delete:
try:
self.HIDDENNOTES.remove(HN)
except ValueError:
LOGGER.debug("'%s' not in hiddenotes", HN)
LOGGER.debug(self.HIDDENNOTES)
self.HIDDENNOTES.append(reversing_note)
else:
try:
self.HIDDENNOTES.remove(reversing_note)
except ValueError:
self.HIDDENNOTES.append(HN)
if one_only:
while self.HIDDENNOTES.count(HN) > 1:
self.HIDDENNOTES.remove(HN)
def clearHiddenNotes(self):
self.HIDDENNOTES = []
def updateBilling(self, tone):
self.billdate = localsettings.currentDay()
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)
def checkExemption(self):
if (self.exemption == "S" and
self.getAge(self.treatment_course.accd)[0] > 19):
self.exemption = ""
self.load_warnings.append(_("Student Exemption removed"))
else:
return True
@property
def name_id(self):
return u"%s - %s" % (
self.name, self.serialno)
@property
def name(self):
return u"%s %s %s" % (
self.title, self.fname, self.sname)
@property
def psn(self):
'''
previous surname
'''
try:
return self.previous_surnames[0]
except IndexError:
return ""
@property
def previous_surnames(self):
if self._previous_surnames is None:
db = connect.connect()
cursor = db.cursor()
cursor.execute(PSN_QUERY, (self.serialno,))
self._previous_surnames = [s[0] for s in cursor.fetchall()]
cursor.close()
return self._previous_surnames
@property
def n_family_members(self):
if self._n_family_members is None:
db = connect.connect()
cursor = db.cursor()
cursor.execute(FAMILY_COUNT_QUERY, (self.familyno,))
self._n_family_members = cursor.fetchone()[0]
return self._n_family_members
@property
def under_capitation(self):
if self.cset != "N":
return False
years, months = self.age_course_start
return years < 17 or (years == 17 and months < 11)
def new_tx_course(self, new_courseno):
self.courseno0 = new_courseno
self.treatment_course = TreatmentCourse(self.serialno, new_courseno)
@property
def COPIED_ATTRIBUTES(self):
'''
these are what is copied over into pt.dbstate
'''
return (patient_query_atts +
exemptionTableAtts + bpeTableAtts + clinical_memos + (
"fees", "estimate_charges", "serialno", "estimates",
"appt_prefs", "treatment_course", "chartgrid"))
@property
def USER_CHANGEABLE_ATTRIBUTES(self):
'''
the attributes, common to pt and the object copy pt.db_state
which is generated during take_snapshot
used to determine whether the patient has been edited.
'''
for att in self.COPIED_ATTRIBUTES:
# if att not in ("treatment_course", "estimates", "chartgrid"):
yield att
@property
def changes(self):
changes = []
for att in self.USER_CHANGEABLE_ATTRIBUTES:
new_value = self.__dict__.get(att, "")
db_value = self.dbstate.__dict__.get(att, "")
if new_value != db_value:
message = "Altered pt.%s" % att.ljust(20)
if att not in ("treatment_course", "estimates"):
message += (
" ORIG = '%s' NEW = '%s'" % (db_value, new_value))
LOGGER.debug(message)
changes.append(att)
return changes
def take_snapshot(self):
# create a snapshot of this class, copying all attributes that the
# user can change
memo = {}
cls = self.__class__
snapshot = cls.__new__(cls)
memo[id(self)] = snapshot
for att, v in self.__dict__.items():
if att in self.COPIED_ATTRIBUTES:
setattr(snapshot, att, deepcopy(v, memo))
self.dbstate = snapshot
LOGGER.debug("snapshot of %s taken" % self)
@property
def course_dentist(self):
'''
returns the course dentist for NHS and private courses, but the
contracted dentist otherwise.
this is used in the daybook for "work done for lists".
'''
if self.cset == "I":
return self.dnt1
if self.dnt2 not in (0, None):
return self.dnt2
return self.dnt1
@property
def has_new_course(self):
if self.treatment_course and self.dbstate.treatment_course is None:
return True
return (self.treatment_course.courseno !=
self.dbstate.treatment_course.courseno)
@property
def tx_hash_tups(self):
'''
a list of unique hashes of all treatment on the current treatment plan
returns a tuple (unique hash, attribute, treatment)
'''
for hash_, att, tx in self.treatment_course._get_tx_hashes():
if re.match("[ul][lr][1-8]", att):
att = self.chartgrid.get(att)
yield hash_, att, tx
@property
def completed_tx_hash_tups(self):
for hash_, att, tx in self.treatment_course.completed_tx_hash_tups:
if re.match("[ul][lr][1-8]", att):
att = self.chartgrid.get(att)
yield hash_, att, tx
@property
def completed_tx_hashes(self):
return list(self.treatment_course.completed_tx_hashes)
@property
def planned_tx_hash_tups(self):
return self.treatment_course.planned_tx_hash_tups
@property
def has_planned_perio_txs(self):
for hash_, att, tx in self.planned_tx_hash_tups:
if att == "perio":
return True
return False
def get_tx_from_hash(self, hash_):
return self.treatment_course.get_tx_from_hash(hash_)
def ests_from_hash(self, hash_):
'''
return all estimate items associated with a unique tx_hash
'''
for est in self.estimates:
for tx_hash in est.tx_hashes:
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'''
try:
serialno = int(sys.argv[len(sys.argv) - 1])
except:
serialno = 1
LOGGER.setLevel(logging.DEBUG)
if "-v" in sys.argv:
verbose = True
else:
verbose = False
pt = patient(serialno)
if verbose:
for att in sorted(pt.__dict__.keys()):
print "%s '%s'" % (att.ljust(20), pt.__dict__[att])
localsettings.loadFeeTables()
pt.fee_table
pt.take_snapshot()
print list(pt.tx_hash_tups)
print pt.treatment_course
print pt.ageYears
print pt.age_course_start
print pt.under_capitation
openmolar-0.6.2/src/openmolar/dbtools/patient_write_changes.py 0000644 0001750 0001750 00000030147 12413077024 024571 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
import sys
import types
import MySQLdb
from openmolar.connect import connect
from openmolar.settings import localsettings
from openmolar.dbtools import patient_class
from openmolar.dbtools import estimates
from openmolar.dbtools.treatment_course import CURRTRT_ATTS
from openmolar.dbtools.treatment_course import UPDATE_CURRTTMT2_QUERY
LOGGER = logging.getLogger("openmolar")
BPE_INS_QUERY = '''insert into bpe (serialno, bpedate, bpe)
values (%s, %s, %s) on duplicate key update bpe=%s'''
EXMPT_INS_QUERY = ('insert into exemptions '
'(serialno, exemption, exempttext, datestamp) '
'values (%s,%s,%s, NOW())')
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))
if changes == []:
LOGGER.warning(
"write changes called, but no changes for patient %d!" % (
pt.serialno)
)
return True
success = True
# set up some booleans to prevent multiple updates of the same data
# example exemption AND exemption text have changed..
exemptionsHandled = False
if pt.HIDDENNOTES != []:
#-- hidden notes is
#-- treatment codes... money, printing etc..
LOGGER.debug("saving hiddennotes")
toNotes(pt.serialno, pt.HIDDENNOTES)
pt.clearHiddenNotes()
sqlcommands = {}
estimate_commands = {}
patchanges, patvalues = [], []
static_changes, static_values = [], []
date_changes, date_values = [], []
nhs_changes, nhs_values = [], []
trtchanges, trtvalues = "", []
# money handled slightly differently. more complex query.
money_changes, money_values = [], []
for change in changes:
if change == "courseno":
pass # these values should never get munged.
elif change in patient_class.money_table_atts:
money_changes.append(change)
money_values.append(pt.__dict__[change])
elif change in patient_class.patientTableAtts:
# patchanges += '%s = %%s,' % change
patchanges.append(change)
patvalues.append(pt.__dict__[change])
elif change in patient_class.date_table_atts:
date_changes.append(change)
date_values.append(pt.__dict__[change])
elif change in patient_class.static_table_atts:
static_changes.append(change.rstrip("st"))
static_values.append(pt.__dict__[change])
elif change in patient_class.nhs_table_atts:
nhs_changes.append(change)
nhs_values.append(pt.__dict__[change])
elif (change in patient_class.exemptionTableAtts and
not exemptionsHandled):
values = (pt.serialno, pt.exemption, pt.exempttext)
sqlcommands['exemptions'] = ((EXMPT_INS_QUERY, values),)
exemptionsHandled = True
elif change == "bpe":
values = (pt.serialno,
pt.bpe[-1][0],
pt.bpe[-1][1],
pt.bpe[-1][1]
)
sqlcommands['bpe'] = ((BPE_INS_QUERY, values),)
elif change == "synopsis":
values = (pt.serialno, pt.synopsis,
localsettings.operator)
sqlcommands['clinical_memos'] = ((SYNOPSIS_INS_QUERY, values),)
elif change == "treatment_course": # patient.CURRTRT_ATTS:
for trt_att in CURRTRT_ATTS:
value = pt.treatment_course.__dict__[trt_att]
existing = pt.dbstate.treatment_course.__dict__[trt_att]
if pt.has_new_course or value != existing:
trtchanges += '%s = %%s ,' % trt_att
trtvalues.append(value)
elif change == "appt_prefs":
pt.appt_prefs.commit_changes()
elif change == "estimates":
pass # dealt with below
if patchanges:
query = "update new_patients SET %s where serialno=%%s" % \
", ".join(["%s = %%s" % change for change in patchanges])
patvalues.append(pt.serialno)
sqlcommands['patients'] = ((query, patvalues),)
if static_changes:
LOGGER.warning(
"applying static_changes %s values %s",
static_changes,
static_values)
query = '''insert into static_chart (pt_sno, %s) values (%%s, %s)
on duplicate key update %s''' % (
", ".join(static_changes),
", ".join(("%s",) * len(static_changes)),
", ".join(["%s = %%s" % change for change in static_changes])
)
values = [pt.serialno] + static_values * 2
sqlcommands['static'] = ((query, values),)
if nhs_changes:
LOGGER.warning(
"applying nhs_changes %s values %s",
nhs_changes,
nhs_values)
query = '''insert into patient_nhs (pt_sno, %s) values (%%s, %s)
on duplicate key update %s''' % (
", ".join(nhs_changes),
", ".join(("%s",) * len(nhs_changes)),
", ".join(["%s = %%s" % change for change in nhs_changes])
)
values = [pt.serialno] + nhs_values * 2
sqlcommands['nhs'] = ((query, values),)
if date_changes:
LOGGER.warning(
"applying date_changes %s values %s",
date_changes,
date_values)
query = '''insert into patient_dates (pt_sno, %s) values (%%s, %s)
on duplicate key update %s''' % (
", ".join(date_changes),
", ".join(("%s",) * len(date_changes)),
", ".join(["%s = %%s" % change for change in date_changes])
)
values = [pt.serialno] + date_values * 2
sqlcommands['patient_dates'] = ((query, values),)
if money_changes:
update_money_values = []
update_query = "update "
for i, change in enumerate(money_changes):
if change in ("money0", "money1"):
diff = pt.__dict__[change] - pt.dbstate.__dict__[change]
update_money_values.append(diff)
update_query += "%s=%s +%%s, " % (change, change)
else:
update_money_values.append(money_values[i])
update_query += "%s=%%s, " % change
LOGGER.warning(
"applying money_changes %s values %s addition_values %s",
money_changes,
money_values,
update_money_values)
query = '''insert into patient_money (pt_sno, %s) values (%%s, %s)
on duplicate key %s''' % (
", ".join(money_changes),
", ".join(("%s",) * len(money_changes)),
update_query.rstrip(", ")
)
values = [pt.serialno] + money_values + update_money_values
LOGGER.debug(query.replace("\n", " "))
LOGGER.debug(values)
sqlcommands['patient_money'] = ((query, values),)
if trtchanges != "":
trtvalues.append(pt.serialno)
trtvalues.append(pt.treatment_course.courseno)
query = UPDATE_CURRTTMT2_QUERY % (trtchanges.strip(","))
sqlcommands['currtrtmt'] = ((query, trtvalues),)
try:
db = connect()
db.autocommit = False
if sqlcommands != {}:
LOGGER.debug(sqlcommands)
cursor = db.cursor()
tables = sqlcommands.keys()
for table in tables:
for query, values in sqlcommands[table]:
try:
cursor.execute(query, values)
except Exception as exc:
LOGGER.error("error executing query %s" % query)
raise exc
cursor.close()
if "estimates" in changes:
estimates.apply_changes(pt, pt.dbstate.estimates, pt.estimates)
db.commit()
except Exception as exc:
LOGGER.exception("rolling back database")
db.rollback()
success = False
raise exc
finally:
db.autocommit = True
return success
def toNotes(serialno, newnotes):
'''
new code with schema 1.9
'''
LOGGER.debug("write changes - toNotes for patient %d" % serialno)
# database version stores max line length of 80chars
query = '''insert into formatted_notes
(serialno, ndate, op1, op2, ntype, note)
VALUES (%s, DATE(NOW()), %s, %s, %s, %s)
'''
notetuplets = []
tstamp = localsettings.currentTime().strftime("%d/%m/%Y %T")
notetuplets.append(
("opened", "System date - %s" % tstamp))
for ntype, note in newnotes:
while len(note) > 79:
if " " in note[:79]:
pos = note[:79].rindex(" ")
#--try to split nicely
elif "," in note[:79]:
pos = note[:79].rindex(",")
#--try to split nicely
else:
pos = 79
#--ok, no option (unlikely to happen though)
notetuplets.append((ntype, note[:pos]))
note = note[pos + 1:]
notetuplets.append((ntype, note + "\n"))
notetuplets.append(
("closed", "%s %s" % (localsettings.operator, tstamp)))
values = []
ops = localsettings.operator.split("/")
op1 = ops[0]
try:
op2 = ops[1]
except IndexError:
op2 = None
for ntype, noteline in notetuplets:
values.append((serialno, op1, op2, ntype, noteline))
rows = 0
if values:
db = connect()
cursor = db.cursor()
# this (superior code?) didn't work on older MySQLdb versions.
# rows = cursor.executemany(query, tuple(values))
for value in values:
rows += cursor.execute(query, value)
cursor.close()
db.commit()
return rows > 0
def discreet_changes(pt, changes):
'''
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")
return all_changes(pt, changes)
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
openmolar-0.6.2/src/openmolar/dbtools/paymentHistory.py 0000644 0001750 0001750 00000014054 12350030721 023252 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from __future__ import division
from openmolar.settings import localsettings
from openmolar.connect import connect
HEADERS = (
_("Date"),
_("Dentist"),
_("Patient"),
_("Code"),
_("Cash"),
_("Cheque"),
_("Card"),
_("Unknown"),
_("Amount")
)
QUERY = '''
select DATE_FORMAT(cbdate, %s), dntid, descr, code, amt
from cashbook where ref=%s order by cbdate desc
'''
SUMMARY_QUERY = '''
select DATE_FORMAT(cbdate, %s), dntid, code, amt
from cashbook where ref=%s and (code<10 or code>123)
and cbdate >= %s order by cbdate
'''
def summary_details(sno, start_date):
values = (localsettings.OM_DATE_FORMAT, "%06d" % sno, start_date)
db = connect()
cursor = db.cursor()
cursor.execute(SUMMARY_QUERY, values)
rows = cursor.fetchall()
cursor.close()
claimNo = len(rows)
if claimNo == 0:
return "No Payments Found"
retarg = ''
retarg += ''
total = 0
for i, row in enumerate(rows):
if i % 2 == 0:
retarg += ''
else:
retarg += ' '
#-- a row is (date,sno,dnt,patient,code,amount)
retarg += '%s | ' % (row[0])
retarg += '%s | ' % localsettings.ops.get(row[1])
CODE = localsettings.cashbookCodesDict.get(row[2], "UNKNOWN")
retarg += '%s | ' % CODE
amt = row[3]
retarg += '%s | ' % localsettings.formatMoney(amt)
retarg += ' \n'
total += amt
retarg += '''''' % (
localsettings.formatMoney(total))
retarg += ' '
return retarg
def details(sno):
'''
returns an html page showing pt's payment History
'''
values = (localsettings.OM_DATE_FORMAT, "%06d" % sno)
db = connect()
cursor = db.cursor()
cursor.execute(QUERY, values)
rows = cursor.fetchall()
cursor.close()
claimNo = len(rows)
if claimNo == 0:
return "No Payments Found"
retarg = ''
retarg += ''
for header in HEADERS:
retarg += "%s | " % header
retarg += ' '
odd = True
total, cashTOT, chequeTOT, cardTOT, otherTOT = 0, 0, 0, 0, 0
for row in rows:
if odd:
retarg += ''
odd = False
else:
retarg += ' '
odd = True
#-- a row is (date,sno,dnt,patient,code,amount)
retarg += '%s | ' % (row[0])
retarg += '%s | ' % localsettings.ops.get(row[1])
retarg += '%s | ' % row[2]
CODE = localsettings.cashbookCodesDict.get(row[3], "UNKNOWN")
retarg += '%s | ' % CODE
amt = row[4]
amt_str = localsettings.formatMoney(amt)
if "CASH" in CODE:
retarg += '%s | ' % amt_str
cashTOT += amt
retarg += " | " * 3
elif "CHEQUE" in CODE:
retarg += ' | %s | ' % amt_str
chequeTOT += amt
retarg += " | " * 2
elif "CARD" in CODE:
retarg += " | " * 2
retarg += '%s | ' % amt_str
cardTOT += amt
retarg += " | "
else:
retarg += " | " * 3
retarg += '%s | ' % amt_str
otherTOT += amt
retarg += '%s | ' % amt_str
retarg += ' \n'
total += amt
retarg += ''' |
TOTAL |
%s |
%s |
%s |
%s |
%s | ''' % (
localsettings.formatMoney(cashTOT),
localsettings.formatMoney(chequeTOT),
localsettings.formatMoney(cardTOT),
localsettings.formatMoney(otherTOT),
localsettings.formatMoney(total))
retarg += ' '
return retarg
if __name__ == "__main__":
from datetime import date
print summary_details(1, date(2000, 1, 1)).encode("ascii", "replace")
openmolar-0.6.2/src/openmolar/dbtools/phrasebook.py 0000644 0001750 0001750 00000011431 12320217277 022356 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import datetime
import logging
from openmolar.connect import connect
CHECK_INTERVAL = 300 # update phrasebook every 5 minutes
BLANK_PHRASEBOOK = '''
An Example Phrase!
Another Phrase
'''
ALL_BOOKS_QUERY = "select distinct clinician_id from phrasebook"
QUERY = "select phrases from phrasebook where clinician_id=%s"
UPDATE_QUERY = "update phrasebook set phrases = %s where clinician_id = %s"
INSERT_QUERY = "insert into phrasebook (phrases, clinician_id) values(%s, %s)"
LOGGER = logging.getLogger("openmolar")
class Phrasebooks(object):
_books = {}
@property
def global_phrasebook(self):
return self.book(0)
def book(self, index):
book = self._books.get(index, None)
if book is None:
book = Phrasebook(index)
self._books[index] = book
return book
def has_book(self, index):
return index in self._books
def has_phrasebook(self, index):
return self.book(index).has_data
def get_all_books(self):
self._books = {} # forget any loaded books
db = connect()
cursor = db.cursor()
cursor.execute(ALL_BOOKS_QUERY)
rows = cursor.fetchall()
ixs = []
for row in rows:
ixs.append(row[0])
cursor.close()
for ix in ixs:
yield self.book(ix)
def update_database(self, xml, clinician_id):
db = connect()
cursor = db.cursor()
result = cursor.execute(UPDATE_QUERY, (xml, clinician_id))
cursor.close()
self._books = {} # forget any loaded books
return result
def create_book(self, clinician_id):
db = connect()
cursor = db.cursor()
result = cursor.execute(INSERT_QUERY, (BLANK_PHRASEBOOK, clinician_id))
cursor.close()
self._books = {} # forget any loaded books
return result
class Phrasebook(object):
_xml = None
_time = datetime.datetime.now()
def __init__(self, ix):
self.ix = ix
@property
def loaded(self):
return self._xml is not None
@property
def refresh_needed(self):
now = datetime.datetime.now()
return now - self._time > datetime.timedelta(0, CHECK_INTERVAL)
@property
def xml(self):
if not self.loaded or self.refresh_needed:
LOGGER.info("(re)loading phrasebook %s from database" % self.ix)
db = connect()
cursor = db.cursor()
cursor.execute(QUERY, (self.ix,))
rows = cursor.fetchone()
self._xml = rows[0] if rows else BLANK_PHRASEBOOK
cursor.close()
self._time = datetime.datetime.now()
return self._xml
@property
def has_data(self):
return self.xml != BLANK_PHRASEBOOK
PHRASEBOOKS = Phrasebooks()
if __name__ == "__main__":
print PHRASEBOOKS.global_phrasebook
print PHRASEBOOKS.book(1)
openmolar-0.6.2/src/openmolar/dbtools/plan_data.py 0000644 0001750 0001750 00000006777 12320217277 022165 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
from openmolar import connect
from openmolar.settings import localsettings
LOGGER = logging.getLogger("openmolar")
planDBAtts = ("serialno", "plantype", "band", "grosschg", "discount", "netchg",
"catcode", "planjoin", "regno")
class PlanData(object):
'''
a custom class to hold data about the patient's maintenance plan
'''
def __init__(self, sno):
self.serialno = sno
self.plantype = None
self.band = None
self.grosschg = 0
self.discount = 0
self.netchg = 0
self.catcode = None
self.planjoin = None
self.regno = None
#-- a variable to indicate if getFromDbhas been run
self.retrieved = False
def __repr__(self):
return "%d,%s,%s,%s,%s,%s,%s,%s,%s" % (
self.serialno, self.plantype, self.band, self.grosschg, self.discount,
self.netchg, self.catcode, self.planjoin, self.regno)
def getFromDB(self):
try:
db = connect.connect()
cursor = db.cursor()
query = '''SELECT %s,%s,%s,%s,%s,%s,%s,%s from plandata
where serialno=%s''' % (planDBAtts[1:] + (self.serialno,))
cursor.execute(query)
row = cursor.fetchone()
cursor.close()
i = 1
if row:
for val in row:
if val:
att = planDBAtts[i]
if att == "planjoin":
self.planjoin = localsettings.formatDate(val)
else:
self.__dict__[att] = val
i += 1
self.retrieved = True
except Exception as exc:
LOGGER.exception("error loading from plandata")
if __name__ == "__main__":
LOGGER.setLevel(logging.DEBUG)
pd = PlanData(1)
pd.getFromDB()
print pd
openmolar-0.6.2/src/openmolar/dbtools/queries.py 0000644 0001750 0001750 00000006325 12401724313 021676 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
PATIENT_QUERY_FIELDS = (
"money0", "money1", "money2", "money3", "money4", "money5", "money6", "money7", "money8", "money9", "money10",
"pd0", "pd1", "pd2", "pd3", "pd4", "pd5", "pd6", "pd7", "pd8", "pd9", "pd10", "pd11", "pd12", "pd13",
"pd14", "sname", "fname", "title", "sex", "dob", "addr1", "addr2", "addr3", "pcde", "tel1", "tel2",
"occup", "nhsno", "cnfd", "cset", "dnt1", "dnt2", "courseno0",
"ur8", "ur7", "ur6", "ur5", "ur4", "ur3", "ur2", "ur1", "ul1", "ul2", "ul3",
"ul4", "ul5", "ul6", "ul7", "ul8", "ll8", "ll7", "ll6", "ll5", "ll4", "ll3",
"ll2", "ll1", "lr1", "lr2", "lr3", "lr4", "lr5", "lr6", "lr7", "lr8", "dent0",
"dent1", "dent2", "dent3", "billdate", "billct", "billtype",
"money11", "familyno", "memo", "town", "county", "mobile", "fax", "email1",
"email2", "status", "initaccept", "lastreaccept",
"lastclaim", "expiry", "cstatus", "transfer", "pstatus"
)
PATIENT_QUERY = '''SELECT %s
from new_patients
left join patient_money on serialno = patient_money.pt_sno
left join static_chart on serialno = static_chart.pt_sno
left join patient_dates on serialno = patient_dates.pt_sno
left join patient_nhs on serialno = patient_nhs.pt_sno
where serialno = %%s''' % ", ".join(PATIENT_QUERY_FIELDS)
FUTURE_EXAM_QUERY = '''select count(*) from aslot
where serialno=%s
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'
openmolar-0.6.2/src/openmolar/dbtools/recall.py 0000644 0001750 0001750 00000014323 12353117723 021467 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import sys
import types
from openmolar.settings import localsettings
from openmolar.connect import connect
from datetime import date
HEADERS = (
_("Letter No"), _("Serial No"), _("Title"), _("First Name"), _("Surname"),
_("Age"),
_("Address") + " 1", _("Address") + " 2", _("Address") + " 3", _("Town"),
_("County"), _("PostCode"), _("Dentist"), _("Family No"), _("Recall Date"))
# note the word CONDITIONS in this query - replaced dynamically at runtime
RECALL_QUERY = '''
select new_patients.serialno, title, fname, sname, dnt1, familyno, dob,
addr1, addr2, addr3, town, county, pcde, recdent
from new_patients join appt_prefs
on new_patients.serialno = appt_prefs.serialno
where CONDITIONS and status != "DECEASED"
order by familyno DESC, addr1, dob, fname, sname'''
class RecalledPatient(object):
'''
a data object to store a recalled patient's details
'''
def __init__(self, letterno, row):
self.letterno = letterno
self.grouped = False
self.serialno = row[0]
self.title = row[1].title()
self.fname = row[2].title()
self.sname = row[3].title()
self.dnt1 = localsettings.ops.get(row[4], "??")
if row[5] == 0:
self.familyno = None
else:
self.familyno = row[5]
self._dob = row[6]
self.addr1 = row[7].strip()
self.addr2 = row[8] if row[8] is not None else ""
self.addr3 = row[9] if row[9] is not None else ""
self.town = row[10] if row[10] is not None else ""
self.county = row[11] if row[11] is not None else ""
self.pcde = row[12] if row[12] is not None else ""
self.recd = row[13]
def __getitem__(self, pos):
if pos == 0:
return self.letterno
elif pos == 1:
return self.serialno
elif pos == 2:
return self.title
elif pos == 3:
return self.fname
elif pos == 4:
return self.sname
elif pos == 5:
return self.age
elif pos == 6:
return self.addr1
elif pos == 7:
return self.addr2
elif pos == 8:
return self.addr3
elif pos == 9:
return self.town
elif pos == 10:
return self.county
elif pos == 11:
return self.pcde
elif pos == 14:
return self.recd
elif pos == 12:
return self.dnt1
elif pos == 13:
return self.familyno
else:
raise IndexError
@property
def age(self):
'''
return the age in string form
'''
today = localsettings.currentDay()
try:
nextbirthday = date(today.year, self._dob.month, self._dob.day)
except ValueError: # leap year!
nextbirthday = date(today.year, self._dob.month, self._dob.day - 1)
ageYears = today.year - self._dob.year
if nextbirthday > today:
ageYears -= 1
return ageYears
def __len__(self):
return 15
def __cmp__(self, other):
'''
allow comparison based on family number and address line 1
'''
if not isinstance(self, type(other)) or self.familyno in (None, 0):
return cmp(0, 1)
else:
return cmp(
(self.familyno, self.addr1),
(other.familyno, other.addr1)
)
def __repr__(self):
'''
represent the object
'''
return "%s %s %s" % (self.serialno, self.sname, self.fname)
def getpatients(conditions="", values=()):
'''
returns patients with a recall between the two dates
'''
assert isinstance(conditions, bytes), "conditions must be a string"
assert isinstance(values, tuple), "values must be a tuple"
query = RECALL_QUERY.replace("CONDITIONS", conditions)
db = connect()
cursor = db.cursor()
cursor.execute(query, values)
rows = cursor.fetchall()
cursor.close()
patients = []
letterno = 1
patient = None
for row in rows:
prev_patient = patient
patient = RecalledPatient(letterno, row)
if patient == prev_patient:
letterno -= 1
patient.letterno = letterno
patient.grouped = True
patients[-1].grouped = True
letterno += 1
patients.append(patient)
return patients
if __name__ == "__main__":
localsettings.initiate()
conditions = "recdent>=%s and recdent<=%s and dnt1=%s"
values = date(2012, 7, 1), date(2012, 7, 31), 6
patients = getpatients(conditions, values)
print patients
openmolar-0.6.2/src/openmolar/dbtools/referral.py 0000644 0001750 0001750 00000013111 12350030721 022006 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
#
#
# Copyright (c) 2009-2014 Neil Wallace # #
#
# 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 . # #
#
#
import time
import datetime
from collections import namedtuple
from openmolar.settings import localsettings
from openmolar.connect import connect
QUERY = "SELECT description FROM referral_centres"
ADDRESS_QUERY = '''
SELECT greeting, addr1, addr2, addr3, addr4, addr5, addr6, addr7
FROM referral_centres where description = %s'''
EDIT_QUERY = '''SELECT ix, description, greeting, addr1, addr2,
addr3, addr4, addr5, addr6, addr7 FROM referral_centres order by ix'''
UPDATE_QUERY = '''UPDATE referral_centres
SET description=%s, greeting=%s, addr1=%s, addr2=%s,
addr3=%s, addr4=%s, addr5=%s, addr6=%s, addr7=%s WHERE ix=%s'''
INSERT_QUERY = '''INSERT INTO referral_centres
(description, greeting, addr1, addr2, addr3, addr4, addr5, addr6, addr7)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)'''
DELETE_QUERY = 'DELETE FROM referral_centres WHERE ix=%s'
HTML = '''
%s
%s
%s
%s %s %s - %s %s
%s
%s
%s
'''
ReferralCentre = namedtuple('ReferralCentre',
('ix', 'description', 'greeting', 'addr1', 'addr2',
'addr3', 'addr4', 'addr5', 'addr6', 'addr7')
)
def getDescriptions():
descriptions = []
db = connect()
cursor = db.cursor()
cursor.execute(QUERY)
for row in cursor.fetchall():
descriptions.append(row[0])
cursor.close()
return descriptions
def get_referral_centres():
db = connect()
cursor = db.cursor()
cursor.execute(EDIT_QUERY)
for row in cursor.fetchall():
yield ReferralCentre(*row)
cursor.close()
def insert_centres(centres):
values = []
for centre in centres:
values.append((centre.description,
centre.greeting,
centre.addr1,
centre.addr2,
centre.addr3,
centre.addr4,
centre.addr5,
centre.addr6,
centre.addr7)
)
if values != []:
db = connect()
cursor = db.cursor()
cursor.executemany(INSERT_QUERY, values)
cursor.close()
def update_centres(centres):
values = []
for centre in centres:
values.append((centre.description,
centre.greeting,
centre.addr1,
centre.addr2,
centre.addr3,
centre.addr4,
centre.addr5,
centre.addr6,
centre.addr7,
centre.ix)
)
if values != []:
db = connect()
cursor = db.cursor()
cursor.executemany(UPDATE_QUERY, values)
cursor.close()
def delete_centres(centres):
values = []
for centre in centres:
values.append((centre.ix,))
if values != []:
db = connect()
cursor = db.cursor()
cursor.executemany(DELETE_QUERY, values)
cursor.close()
def getHtml(description, pt):
'''
get the HTML for a letter to
referral_centre identified by description about this pt
'''
descriptions = []
db = connect()
cursor = db.cursor()
cursor.execute(ADDRESS_QUERY, (description,))
row = cursor.fetchone()
cursor.close()
if not row:
return HTML
greeting, addr1, addr2, addr3, addr4, addr5, addr6, addr7 = row
tel = _("Telephone") + " :- "
for i, val in enumerate((pt.tel1, pt.tel2, pt.mobile)):
if val != "":
tel += "%s %s " % (
(_("home"), _("work "), _("mobile "))[i],
val)
return HTML % (
" ".join(
[a for a in (
addr1, addr2, addr3, addr4, addr5, addr6, addr7) if a != ""]),
localsettings.longDate(localsettings.currentDay()),
greeting,
pt.title.title(), pt.fname.title(), pt.sname.title(),
_("D.O.B."), localsettings.formatDate(pt.dob),
",".join(
[a for a in
(pt.addr1, pt.addr2, pt.addr3, pt.town,
pt.county, pt.pcde) if a != ""]),
tel,
_("Yours Sincerely"))
if __name__ == "__main__":
localsettings.initiate()
from openmolar.dbtools import patient_class
pt = patient_class.patient(4)
d = getDescriptions()
print d
print getHtml(d[0], pt)
openmolar-0.6.2/src/openmolar/dbtools/schema_version.py 0000644 0001750 0001750 00000007336 12350030721 023225 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
from openmolar import connect
from openmolar.settings import localsettings
LOGGER = logging.getLogger("openmolar")
SELECT_QUERY = 'select max(data) from settings where value = "Schema_Version"'
INSERT_QUERY = '''insert into settings (value,data,modified_by,time_stamp)
values (%s, %s, %s, NOW())'''
DELETE_QUERY = 'delete from settings where value = "compatible_clients"'
COMPAT_QUERY = '''insert into settings (value, data, modified_by, time_stamp)
values ("compatible_clients", %s, 'Update script', NOW())'''
def getVersion():
try:
db = connect.connect()
cursor = db.cursor()
cursor.execute(SELECT_QUERY)
version = cursor.fetchone()[0]
except connect.ProgrammingError as ex:
LOGGER.warning("no settings table! %s" % ex)
LOGGER.warning("schema assumed to be 1.0")
version = "1.0"
localsettings.DB_SCHEMA_VERSION = version
return version
def clientCompatibility(client_schema):
rows = ()
try:
db = connect.connect()
cursor = db.cursor()
query = 'select data from settings where value = "compatible_clients"'
cursor.execute(query)
rows = cursor.fetchall()
except connect.ProgrammingError as ex:
LOGGER.exception("client_schema not found")
for row in rows:
if row[0] == client_schema:
return True
def update(schemas, user):
'''
updates the schema version,
pass a list of compatible clients version and a user
eg. updateSchemaVersion (("1.1","1.2"), "admin script")
the first in the list is the minimum allowed,
the last is the current schema
'''
latest_schema = schemas[-1]
db = connect.connect()
cursor = db.cursor()
values = ("Schema_Version", latest_schema, user)
LOGGER.info("making the db aware of it's schema version")
cursor.execute(INSERT_QUERY, values)
LOGGER.info("disabling ALL old clients")
cursor.execute(DELETE_QUERY)
LOGGER.info("enabling compatible clients")
for schema in schemas:
values = (schema,)
cursor.execute(COMPAT_QUERY, values)
return True
openmolar-0.6.2/src/openmolar/dbtools/search.py 0000644 0001750 0001750 00000012700 12353117723 021467 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
'''this script connects to the database and performs searches'''
import datetime
import logging
import sys
from openmolar.connect import connect
from openmolar.settings import localsettings
LOGGER = logging.getLogger("openmolar")
ALL_PATIENTS_QUERY = \
'''SELECT serialno, status, title, fname, sname, dob, addr1, addr2, town,
pcde, tel1, tel2, mobile FROM new_patients ORDER BY sname, fname'''
def all_patients():
db = connect()
cursor = db.cursor()
cursor.execute(ALL_PATIENTS_QUERY)
results = cursor.fetchall()
cursor.close()
return results
def getcandidates(dob, addr, tel, sname, similar_sname, fname,
similar_fname, pcde):
'''
this searches the database for patients matching the given fields
'''
conditions = []
values = []
if addr != '':
conditions.append('(ADDR1 like %s or ADDR2 like %s or town like %s)')
values += ["%" + addr + "%"] * 3
if tel != '':
conditions.append('tel1 like %s or tel2 like %s or mobile like %s')
values += ["%" + tel + "%"] * 3
if dob != datetime.date(1900, 1, 1):
conditions.append('dob = %s')
values.append(dob)
if pcde != '':
conditions.append('pcde like %s')
values.append("%" + pcde + "%")
if sname != '':
if similar_sname:
conditions.append('sname sounds like %s')
values.append(sname)
else:
sname += "%"
if "'" in sname:
conditions.append('(sname like %s or sname like %s)')
values.append(sname)
values.append(sname.replace("'", ""))
elif sname[:1] == "o":
conditions.append('(sname like %s or sname like %s)')
values.append(sname)
values.append("o'" + sname[1:])
elif sname[:2] == "mc":
conditions.append('(sname like %s or sname like %s)')
values.append(sname)
values.append(sname.replace("mc", "mac"))
elif sname[:3] == "mac":
conditions.append('(sname like %s or sname like %s)')
values.append(sname)
values.append(sname.replace("mac", "mc"))
else:
conditions.append('sname like %s')
values.append(sname)
if fname != '':
if similar_fname:
conditions.append('fname sounds like %s')
values.append(fname)
else:
conditions.append('fname like %s')
values.append(fname + "%")
if conditions:
conditional = "WHERE %s ORDER BY" % " AND ".join(conditions)
query = ALL_PATIENTS_QUERY.replace("ORDER BY", conditional)
LOGGER.debug(query.replace("\n", " "))
LOGGER.debug(values)
db = connect()
cursor = db.cursor()
cursor.execute(query, tuple(values))
results = cursor.fetchall()
cursor.close()
return results
else:
return ()
def getcandidates_from_serialnos(list_of_snos):
'''
this probably never actually gets called now, as it relates to a time when
"double appointments" were commonplace.
'''
format_snos = ",". join(('%s',) * len(list_of_snos)) # %s,%s,%s
conditional = "WHERE serialno in (%s) ORDER BY" % format_snos
query = ALL_PATIENTS_QUERY.replace("ORDER BY", conditional)
db = connect()
cursor = db.cursor()
cursor.execute(query, list_of_snos)
results = cursor.fetchall()
cursor.close()
return results
if __name__ == '__main__':
values = (datetime.date(1969, 12, 9), "Gables", "772378",
"wallace", "", "neil", "", "IV2")
new_vals = getcandidates(*values)
for candidate in new_vals:
print candidate
snos = (1, 2, 3)
for candidate in getcandidates_from_serialnos(snos):
print candidate
openmolar-0.6.2/src/openmolar/dbtools/standard_letter.py 0000644 0001750 0001750 00000010707 12355234216 023405 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
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 = '''
%s
%%s
%%s
%%s
%s
%%s %%s,
%s
%%s
%s
''' % (" " * 6, " " * 4, " " * (12), " " * 6)
def getHtml(pt):
return TEMPLATE % (pt.name,
pt.address.replace("\n", " "),
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() openmolar-0.6.2/src/openmolar/dbtools/treatment_course.py 0000644 0001750 0001750 00000043525 12350030721 023603 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import logging
from openmolar import connect
from openmolar.settings import localsettings
LOGGER = logging.getLogger("openmolar")
CURRTRT_NON_TOOTH_ATTS = (
'xray', 'perio', 'anaes', 'other', 'ndu', 'ndl', 'odu', 'odl', 'custom')
UPPERS = ('ur8', 'ur7', 'ur6', 'ur5', 'ur4', 'ur3', 'ur2', 'ur1',
'ul1', 'ul2', 'ul3', 'ul4', 'ul5', 'ul6', 'ul7', 'ul8')
LOWERS = ('lr8', 'lr7', 'lr6', 'lr5', 'lr4', 'lr3', 'lr2', 'lr1',
'll1', 'll2', 'll3', 'll4', 'll5', 'll6', 'll7', 'll8')
CURRTRT_ROOT_ATTS = CURRTRT_NON_TOOTH_ATTS + UPPERS + LOWERS
CURRTRT_ATTS = (
'courseno', 'xraypl', 'periopl', 'anaespl', 'otherpl',
'ndupl', 'ndlpl', 'odupl', 'odlpl', "custompl", 'xraycmp',
'periocmp', 'anaescmp', 'othercmp', 'nducmp', 'ndlcmp',
'oducmp', 'odlcmp', "customcmp",
'ur8pl', 'ur7pl', 'ur6pl', 'ur5pl', 'ur4pl', 'ur3pl', 'ur2pl', 'ur1pl',
'ul1pl', 'ul2pl', 'ul3pl', 'ul4pl', 'ul5pl', 'ul6pl', 'ul7pl', 'ul8pl',
'll8pl', 'll7pl', 'll6pl', 'll5pl', 'll4pl', 'll3pl', 'll2pl', 'll1pl',
'lr1pl', 'lr2pl', 'lr3pl', 'lr4pl', 'lr5pl', 'lr6pl', 'lr7pl', 'lr8pl',
'ur8cmp', 'ur7cmp', 'ur6cmp', 'ur5cmp',
'ur4cmp', 'ur3cmp', 'ur2cmp', 'ur1cmp',
'ul1cmp', 'ul2cmp', 'ul3cmp', 'ul4cmp',
'ul5cmp', 'ul6cmp', 'ul7cmp', 'ul8cmp',
'll8cmp', 'll7cmp', 'll6cmp', 'll5cmp',
'll4cmp', 'll3cmp', 'll2cmp', 'll1cmp',
'lr1cmp', 'lr2cmp', 'lr3cmp', 'lr4cmp',
'lr5cmp', 'lr6cmp', 'lr7cmp', 'lr8cmp',
'examt', 'examd', 'accd', 'cmpd', 'ftr')
QUERY = "SELECT "
for field in CURRTRT_ATTS:
QUERY += "%s, " % field
QUERY = QUERY.rstrip(", ")
QUERY += " from currtrtmt2 where serialno=%s and courseno=%s"
MAX_COURSE_QUERY = "select max(courseno) from currtrtmt2 where serialno=%s"
DATE_QUERY = "select accd, cmpd, examd from currtrtmt2 where courseno=%s"
UPDATE_DATES_QUERY = "update currtrtmt2 set accd=%s, cmpd=%s where courseno=%s"
UPDATE_CURRTTMT2_QUERY = (
'UPDATE currtrtmt2 SET %s WHERE serialno=%%s and courseno=%%s')
DELETE_CURRTTMT2_QUERY = (
'DELETE from currtrtmt2 WHERE serialno=%s and courseno=%s')
UPDATE_ESTS_COURSENO_QUERY = (
'UPDATE newestimates SET courseno=%s WHERE courseno=%s')
def get_course_dates(courseno):
db = connect.connect()
cursor = db.cursor()
cursor.execute(DATE_QUERY, (courseno, ))
row = cursor.fetchone()
cursor.close()
return row
def update_course_dates(accd, cmpd, courseno):
db = connect.connect()
cursor = db.cursor()
cursor.execute(UPDATE_DATES_QUERY, (accd, cmpd, courseno, ))
cursor.close()
def update_estimate_courseno(courseno_orig, courseno_new):
db = connect.connect()
cursor = db.cursor()
cursor.execute(UPDATE_ESTS_COURSENO_QUERY, (courseno_new, courseno_orig))
cursor.close()
def update_course(query_insert, values, serialno, courseno):
assert len(values) == query_insert.count("=")
query = UPDATE_CURRTTMT2_QUERY % query_insert
values.append(serialno)
values.append(courseno)
db = connect.connect()
cursor = db.cursor()
result = cursor.execute(query, values)
cursor.close()
return result
def delete_course(serialno, courseno):
db = connect.connect()
cursor = db.cursor()
cursor.execute(DELETE_CURRTTMT2_QUERY % (serialno, courseno))
cursor.close()
class TreatmentCourse(object):
def __init__(self, sno, courseno):
'''
initiate the class with default variables, then load from database
'''
self.dbstate = None
self.serialno = sno
self.courseno = courseno
self.xraypl = ''
self.periopl = ''
self.anaespl = ''
self.otherpl = ''
self.ndupl = ''
self.ndlpl = ''
self.odupl = ''
self.odlpl = ''
self.custompl = ''
self.xraycmp = ''
self.periocmp = ''
self.anaescmp = ''
self.othercmp = ''
self.nducmp = ''
self.ndlcmp = ''
self.oducmp = ''
self.odlcmp = ''
self.customcmp = ''
self.ur8pl = ''
self.ur7pl = ''
self.ur6pl = ''
self.ur5pl = ''
self.ur4pl = ''
self.ur3pl = ''
self.ur2pl = ''
self.ur1pl = ''
self.ul1pl = ''
self.ul2pl = ''
self.ul3pl = ''
self.ul4pl = ''
self.ul5pl = ''
self.ul6pl = ''
self.ul7pl = ''
self.ul8pl = ''
self.ll8pl = ''
self.ll7pl = ''
self.ll6pl = ''
self.ll5pl = ''
self.ll4pl = ''
self.ll3pl = ''
self.ll2pl = ''
self.ll1pl = ''
self.lr1pl = ''
self.lr2pl = ''
self.lr3pl = ''
self.lr4pl = ''
self.lr5pl = ''
self.lr6pl = ''
self.lr7pl = ''
self.lr8pl = ''
self.ur8cmp = ''
self.ur7cmp = ''
self.ur6cmp = ''
self.ur5cmp = ''
self.ur4cmp = ''
self.ur3cmp = ''
self.ur2cmp = ''
self.ur1cmp = ''
self.ul1cmp = ''
self.ul2cmp = ''
self.ul3cmp = ''
self.ul4cmp = ''
self.ul5cmp = ''
self.ul6cmp = ''
self.ul7cmp = ''
self.ul8cmp = ''
self.ll8cmp = ''
self.ll7cmp = ''
self.ll6cmp = ''
self.ll5cmp = ''
self.ll4cmp = ''
self.ll3cmp = ''
self.ll2cmp = ''
self.ll1cmp = ''
self.lr1cmp = ''
self.lr2cmp = ''
self.lr3cmp = ''
self.lr4cmp = ''
self.lr5cmp = ''
self.lr6cmp = ''
self.lr7cmp = ''
self.lr8cmp = ''
self.examt = ''
self.examd = ''
self.accd = None
self.cmpd = None
self.ftr = None
# this next line gives me a way to create a Mock Instance of the class
if self.courseno == 0:
return
self.getCurrtrt()
def __repr__(self):
message = "TreatmentCourse for patient %s courseno %s\n" % (
self.serialno, self.courseno)
for att in CURRTRT_ATTS:
value = self.__dict__.get(att, "")
if value != "":
message += " %s,%s\n" % (att, value)
return message
def __cmp__(self, other):
return cmp(unicode(self), unicode(other))
def _non_tooth_items(self, suffix="pl"):
for att in CURRTRT_NON_TOOTH_ATTS:
value = self.__dict__.get(att + suffix, "")
if value != "":
txs = value.split(" ")
for tx in set(txs):
if tx != "":
n = txs.count(tx)
if n != 1:
tx = "%d%s" % (n, tx)
yield att, tx
@property
def non_tooth_plan_items(self):
return list(self._non_tooth_items("pl"))
@property
def non_tooth_cmp_items(self):
items = []
if self.examt != "" and self.examd:
items.append(("exam", self.examt))
return items + list(self._non_tooth_items("cmp"))
def getCurrtrt(self):
db = connect.connect()
cursor = db.cursor()
cursor.execute(QUERY, (self.serialno, self.courseno))
for value in cursor.fetchall():
for i, field in enumerate(CURRTRT_ATTS):
self.__dict__[field] = value[i]
cursor.close()
@property
def underTreatment(self):
return not self.accd in ("", None) and self.cmpd in ("", None)
@property
def max_tx_courseno(self):
db = connect.connect()
cursor = db.cursor()
if cursor.execute(MAX_COURSE_QUERY, (self.serialno,)):
cno = cursor.fetchone()[0]
else:
cno = 0
cursor.close()
return cno
@property
def newer_course_found(self):
return self.max_tx_courseno > self.courseno
@property
def has_exam(self):
return self.examt != "" and self.examd
def setAccd(self, accd):
'''
set the acceptance date (with a pydate)
'''
if accd is None:
accd = localsettings.currentDay()
self.accd = accd
def setCmpd(self, cmpd):
'''
set the completion date (with a pydate)
'''
self.cmpd = cmpd
def set_ftr(self, ftr):
'''
ftr = "Failed to Return"
'''
self.ftr = ftr
@property
def has_treatment_outstanding(self):
for att in CURRTRT_ATTS:
if att[-2:] == "pl":
if self.__dict__[att].strip(" ") != "":
return True
return False
@property
def tx_hashes(self):
return self._get_tx_hashes()
@property
def completed_tx_hash_tups(self):
return self._get_tx_hashes(True)
@property
def completed_tx_hashes(self):
for hash_, att, tx in self._get_tx_hashes(True):
yield hash_
@property
def planned_tx_hash_tups(self):
for tup in self._get_tx_hashes():
if not tup in self.completed_tx_hash_tups:
yield tup
def _get_tx_hashes(self, completed_only=False):
'''
returns a tuple (unique hash, attribute, treatment)
hashes will be unique as multiple identical items are indexed
eg. eg "perio SP AC SP " is hashed as follows
"%sperio1SP"% courseno
"%sperio2SP"% courseno
"%sperio1AC"% courseno
'''
if self.examt != "":
hash_ = localsettings.hash_func(
"%sexam1%s" % (self.courseno, self.examt))
yield (hash_, "exam", self.examt + " ")
for att in CURRTRT_ROOT_ATTS:
treats = self.__dict__[att + "cmp"]
if not completed_only:
treats += " " + self.__dict__[att + "pl"]
treat_list = sorted(treats.split(" "))
prev_tx, count = None, 1
for tx in treat_list:
if tx == "":
continue
if tx != prev_tx:
count = 1
prev_tx = tx
else:
count += 1
hash_ = localsettings.hash_func(
"%s%s%s%s" % (self.courseno, att, count, tx))
yield (hash_, att, tx + " ")
def get_tx_from_hash(self, hash_):
'''
example
imput a hash 039480284098
get back ("ur1", "M")
'''
for tx_hash in self.tx_hashes:
if tx_hash[0] == hash_:
return tx_hash[1], tx_hash[2]
LOGGER.warning("couldn't find treatment %s" % hash_)
LOGGER.debug("listing existing hashes")
for tx_hash in self.tx_hashes:
LOGGER.debug(tx_hash)
return None, None
def pl_txs(self, att):
'''
returns the list of treatments currently planned for this attribute.
eg pl_txs("ul8") may return ["O", "B,CO"]
'''
txs = self.__dict__["%spl" % att].split(" ")
while "" in txs:
txs.remove("")
return txs
def cmp_txs(self, att):
'''
returns the list of treatments currently planned for this attribute.
eg cmp_txs("ul8") may return ["O", "B,CO"]
'''
txs = self.__dict__["%scmp" % att].split(" ")
while "" in txs:
txs.remove("")
return txs
def all_txs(self, att):
'''
returns the list of treatments currently associated with an attribute.
eg all_txs("ul8") may return ["O", "B,CO"]
'''
return self.cmp_txs(att) + self.pl_txs(att)
@property
def course_duration(self):
if not self.cmpd:
return (_("still ongoing"))
else:
days = (self.cmpd - self.accd).days + 1
if days == 1:
return "1 %s" % _("day")
return "%s %s" % (days, _("days"))
def to_html(self, allow_edit=False, days_elapsed=None, completed_only=False):
def sorted_work(work):
items = work.split(" ")
return " ".join(sorted([item for item in items if item != ""]))
if allow_edit:
edit_str = '''%s
%s
''' % (
self.courseno, _("Edit Course Dates"),
self.courseno, _("Edit Treatments"))
else:
edit_str = ""
if days_elapsed is None:
days_str = ""
else:
days_str = " (%s %s)" % (days_elapsed, _("days earlier"))
html = '''
%s %s %s
%s
%s |
%s %s %s %s
|
%s %s |
''' % (
_("Course Number"), self.courseno, days_str,
_("PATIENT FAILED TO RETURN") if self.ftr else "",
edit_str,
_("Opened"), localsettings.formatDate(self.accd),
_("Closed"), localsettings.formatDate(self.cmpd),
_("Duration"), self.course_duration,
)
attributes = ("cmp",) if completed_only else ("pl", "cmp")
#-plan row.
for planned in attributes:
rows = []
if planned == "pl":
bgcolor = ' bgcolor = "#eeeeee"'
header = "%s %s" % (_("Planned"), _("or incomplete"))
else:
bgcolor = ' bgcolor = "#ddeeee"'
header = _("Completed")
if self.examt != "":
exam_details = self.examt
if self.examd:
exam_details += " %s - %s" % (
_("dated"),
localsettings.formatDate(self.examd))
cells = "%s | \n%s | \n" % (
bgcolor, _("Exam"), exam_details)
rows.append(cells)
for att, con_att in (
("perio", _("perio")),
("xray", _('xray')),
("anaes", _('anaes')),
("other", _('other')),
("custom", _("custom")),
('ndu', _("New Denture (upper)")),
('ndl', _("New Denture (lower)")),
('odu', _("Other Denture (upper)")),
('odl', _("Other Denture (lower)")),
):
work = self.__dict__[att + planned]
if work.strip(" ") != "":
cells = "%s | \n%s | \n" % (
bgcolor, con_att, sorted_work(work))
rows.append(cells)
show_chart = False
row1, row2, row3, row4 = "", " ", " ", " "
for att in UPPERS:
work = self.__dict__[att + planned]
row1 += '%s | \n' % sorted_work(work)
row2 += '%s | \n' % (
bgcolor, att.upper())
show_chart = show_chart or work.strip(" ") != ""
for att in LOWERS:
work = self.__dict__[att + planned]
row3 += '%s | \n' % (
bgcolor, att.upper())
row4 += '%s | \n' % sorted_work(work)
show_chart = show_chart or work.strip(" ") != ""
if show_chart:
chart_cells = '''
|
''' % (row1, row2, row3, row4)
rows.append(chart_cells)
row_span = len(rows)
if rows != []:
html += ' \n%s | \n' % (
row_span, bgcolor, header)
for row in rows:
if row == rows[0]:
html += "%s \n" % row
else:
html += "%s \n" % row
html += ' \n'
return html
if __name__ == "__main__":
'''
testing stuff
'''
tc = TreatmentCourse(14469, 45869)
print tc
print tc.non_tooth_plan_items
print tc.non_tooth_cmp_items
print tc.all_txs("ur5")
f = open("/home/neil/out.html", "w")
f.write(tc.to_html())
f.close()
openmolar-0.6.2/src/openmolar/dbtools/writeNewCourse.py 0000644 0001750 0001750 00000004367 12320217277 023220 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
from openmolar.connect import connect
INS_QUERY = 'insert into currtrtmt2 (serialno, accd) values (%s, %s)'
DEL_QUERY = 'delete from currtrtmt2 where serialno = %s and courseno = %s'
def write(serialno, accd):
db = connect()
cursor = db.cursor()
cursor.execute(INS_QUERY, (serialno, accd))
cno = db.insert_id()
cursor.close()
return cno
def delete(serialno, courseno):
db = connect()
cursor = db.cursor()
cursor.execute(DEL_QUERY, (serialno, courseno))
cno = db.insert_id()
cursor.close()
if __name__ == "__main__":
print "started course %d" % write(31720, "20081225")
openmolar-0.6.2/src/openmolar/dbtools/writeNewPatient.py 0000644 0001750 0001750 00000006105 12353243376 023361 0 ustar neil neil 0000000 0000000 #! /usr/bin/env python
# -*- coding: utf-8 -*-
# ############################################################################ #
# # # #
# # Copyright (c) 2009-2014 Neil Wallace # #
# # # #
# # 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 . # #
# # # #
# ############################################################################ #
import MySQLdb
from openmolar import connect
from openmolar.dbtools import patient_class
from openmolar.settings import localsettings
def commit(pt):
sqlcond = ""
values = []
for attr in patient_class.patientTableAtts:
value = pt.__dict__[attr]
if value:
sqlcond += '%s = %%s,' % attr
values.append(value)
sqlcommand = "insert into new_patients SET %s serialno=%%s" % sqlcond
query = "select max(serialno) from new_patients"
Attempts = 0
while True:
db = connect.connect()
cursor = db.cursor()
cursor.execute(query)
currentMax = cursor.fetchone()[0]
if currentMax:
newSerialno = currentMax + 1
else:
newSerialno = 1
try:
cursor.execute(sqlcommand, tuple(values + [newSerialno]))
cursor.close()
db.commit()
break
except connect.IntegrityError as e:
print "error saving new patient, will retry with new serialno"
print e
newSerialno = -1
Attempts += 1
if Attempts > 20:
break
# db.close()
return newSerialno
if __name__ == "__main__":
global pt
from openmolar.dbtools import patient_class
import copy
pt = patient_class.patient(0)
pt.fname = "Norman"
pt.sname = "Wisdom"
# ok - so a trivial change has been made - now write to the database
print commit(pt)
openmolar-0.6.2/src/openmolar/html/ 0000755 0001750 0001750 00000000000 12455462401 017145 5 ustar neil neil 0000000 0000000 openmolar-0.6.2/src/openmolar/html/firstrun/ 0000755 0001750 0001750 00000000000 12455462401 021021 5 ustar neil neil 0000000 0000000 openmolar-0.6.2/src/openmolar/html/firstrun/firstrun0.png 0000644 0001750 0001750 00000020360 12033133562 023457 0 ustar neil neil 0000000 0000000 ‰PNG
IHDR ² ›¹Ri sBIT|dˆ tEXtSoftware gnome-screenshotï¿> IDATxœíÝyxTÕýÇñO6”) %, ‚ì[DÔÅÚMEöÕQ”½úÂâŠ[]¦¸¡$hÅ€`kZH†„,3¿?’gÂ,wB–9áýzžy†™;sï9çνŸœ›!_ 8·„øyÞß= 5Éîï>ÜÛBŽå²Õh³ ° q³–
UZvI¶J÷vIòd ‹(•—MRY¥{›¼ÍÈjy øÖ@åÁUêrRqo—ÂŒ Ì¢TZa’JôÓdËy¹‘ ˜5Py€U1[Åcfd € Ö@å³±Ê!¦òK¿ìÁŒ ,¢äberÿ]32 @P‹ª¸w„XiÅ-TÇŒ Ì"åb%*1G…„Ö]Û ð+Bå“®p•ÿ^Ì-ÄTñ cd £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £d £ÕZeçä(®m¼×Û»¿Tnn®âÚÆ{|¿¯eVÝ;o¾âÚÆ+}Ó–³ZO]:œŸ¯>Wé½Õ1†žÚP]ë-))Ñ
ã~¥ö ‰gµ^cTSí¯kõ¥ÞdeíÑø ·)¾S¢â;%êæ[«¬¬=5¾Ý¸¶ñ’2Rv»Ýíy»Ý®¡Ã¯±4æÁ¶o9‚ížÔZ]ܲ¥¾ü<Óy“¤m›7:wêÔÑçû›7o®¯÷î®òö‹ŠŠ´qÓfõís•ÖoHòzꚬL99ëeŽ=ªÏÿþ…¾ØµSRÕ÷¹¿öU^~¶Ÿ-Ô¼={öê¦ñÔ³G’ÞÙ²QÛ6§©{·®úåø[´gÏÞß~nn®öîýÊí¹¯öíסC‡j|Û5!cØ„ã£Ö‚,44TE;o’Ô(º¡óqxX˜óµë^|YIÉ}Ô)1I«Ÿ\+IÊËËS»Ž]œ¯Ù²í
vµ.o×QýÕ;ï¾çsûïn_-[4×Ãçë“OþªÃùùÎe6›MqmãõÖ†·•”ÜGí5{î½***ò¹L*ŸEÌ{àAuìÒ]]º÷Ô‚…‹T\\,IJß´ECRFêòvÕçUξHå?å½÷þJìÞKïð¡åþŒ{ƒ$)¹Ï çsÇŸÐäi3Ô!¡«:wë¡ûî_àlŸ'/¬{I]“’Õ!¡«¦Ïš£S……~ûâ¯
’çýV•õœ"éÌ}nu̼µÏ[û=mçµ×ßTÞ}Õ±s7½°î%-Y¶B]º÷T§Ä$=¿îÅ€ûöÀƒédgëŠö úñøqIÞ÷a埆ütìm?xû<Þöû?jÙŠ?9_wäÈQ]Þ®£þsà@@}tŒckƒÏ¶;®ŽôèÝW“´æ©§½öé‘%ËtÛonÕ¤‰w(¶ukµ‰Õ”Iõ› ·hñ²’|Çþúá¯-WOÑúÔ·ÝžK};MWOq{Îê±È¹¡2_Û°:¦Ãg{î
Aù;²ÿ½>ýä#yâq-]¾R'Nœt[~ª°PS§ÏÒ‚ûïÓ×{wkÚ”Iºû¾û}®óÍ·6è×7ýR±[+)©»ÞNKw.³Ùl’¤M[¶jsZª6§¥ê«}û´â±ÿó¹L’žXý¤
‹
õ×ÞׯÔõÊÚ³GËV>¦‚‚M›9[)Æh_Ößµæ‰ÇµdÙ
ç‰K’ž{až~jµ:'t²ÜŸÞ”$}úɇÎçæÍ_ ‚‚SúøÃíÚšž¦ý_•ò:îøXÛ6oÔ¦´
úÇ?þ©¥ËVúì‹•6HÞ÷Ûٮו•1ó·+ÛÉÚ³Wýè}-x`žæ/|Xaaaú<ãoºïž¹Îñ
¤oÇ•ðŽËSé›¶h@ÿ~º0&FRàûÐOûÁ×çñš#´ííÎ÷oÞºMݺ&ªMll@}tp죮‰]¼¾ÆáÃ;ôñÛµê±Z²lÅǺT~5%#s—ÆÝxýËÆÝxƒvfdª¸¸¸ÊǪ•¶\;f´Ò6nRII‰$©´¬Lié›tí˜Qní±²97ôêÙãŒ>ûÛ†•1
ôv¨Ê¹·6eÝuÇí
Sïä^²Ûí:uªÀm¹¬L6›MÒéÓ§uí˜QúÛŽ÷½®/7/OŸÿý];f´$é×_§õR'•²²2IÒ½sg«Y³‹t饗hάژ¾Ùç2IÚš¦{˜˜FŠmÝZ³fLSZZº"£¢´}ë&Mšx‡BCC.I*ª˜ýHÒ”Iw©[×DEFFÔWÅÅÅÚ²õÍ»g®š4i¢–-[hÆ´)JKßäõ=óî™ëÖ—M[¶øì‹UÞöÛÙ®×UuŒ™¿»íVEDD¨wr/IÒ·ÿQaaaê×ç*çV²Þ·¤¤î:}ú´óòTú¦Íºvô5’ª¶}ñ´|}‡¤ÿ8 oÿýogÛn¸~lÀ}tpì£
ømë„›Ç+22Rýúöñx¬KÒñ'd³ÙÔ¢yó3–µlÑBeeeúñøñ*«VÚòóË.U›ØX}´ãcIÒ§ŸîT‹-ôóË✯±º97œþùnïµ²
+cêMuŸ{kKx]7À“¦M›H’BBB<.oذ¡Ö®yBkžzZó>¬øvWjÆ´)ºªw²Ç×oHMSAA:wsÿéfßþ¯Õ¡}¼Ê*~’ksI粸¸KuøðaŸË$)çàAuMrßnhh¨Âô;+K«Ÿ\+»Ý®+¯h{F»Z]|q•úãêøñ*--UëØÖÎçbc[+ßåÒie•ûòßÿóÙ«¼í·³]¯«ê3+4ˆ’$9ºRù±ƒÕ¾…‡…ièÁzgû{ºà‚ôŸÙ4p€¤Àöa¥ïxäi?øúûb•·ýv¶ëuUcVé[JÊP-^²L>l¨¢¢ÊÃÑÊ>´Ûí
Ñ‘£Gý¶ÉÓ~ð÷y5r¤žzú~
6D\pA•ú(ý´¬´Ýʹ6**J=’ºëµ××kÆ´ÉnË^{}½zõì¡ÈÈHýïÿ“ø±jµ-£F^%K—+7/OÛß{_wÏž¥ÒÒçr«Çb ç†Ê¬l£
ùåòÞê;÷Ö¦ ¼´èÍfÓÍ·þV™»q^„¢F«A”çË»¿ÌRvNކ
¬ð°0çmÄÕ)JMKWii©ÊÊÊ’{ðáGtäÈQ}ÿýzdñR}ÏeRùÉéáG—èäÉ“ÊÏ?¢»¦LךµOËf³©ÌfSÄy*--uþâ´¸¤äŒ6ÒǵúóÎ;OÃS†ê¡GëØ±cÊÍÍÕÒå+5Ú%´+{hÑ£n};fŒÏ¾økƒ?®×
+cæ¯}VÛïK }»*¹—rÒ‹/¿¢1ŸÉ÷>lÐ üÒÒß>Ý©ââb=ûüº*µÓßçqÐ ú×·ßê…_ÖõcǺ½·ªû¯ºÚ.IwÏž©gŸ{^«þ¼ZÙ99:ÇWýYÏ<÷œæÌœ.IU>Vjܸ±’“{iúÌ9Ꚙ¨‹.r(«Çb ç†Êªr¼ûè1P•sUm02È¢¢¢ôàó4uÆl]Ù>A«Ÿ\«åKõøÚ7×oЀþ}Ý~”¤ÁèäÉ“úhÇDzÙÊg]½“{iÈð1úZuìÐAÓ§Lò¹L*¿_PP ½ûiàÐájÚ´‰î»{Ž¢££5gÖûÕ-êÕg€ÂÃÃÕ;¹—&Üöû³êOãÆÕöòŸ+ÑåÉCæ+**RWõ¤¡WR|»vš>u²Ç÷KÒÀý5dø]3æ:uéÜY“&Þî³/VÚà‹ÕõÂטùk_ í÷%¾EFFjÐÀþ
Qr¯žn˼íØ˜Fš1m²þxç$õî7H}ªx Çßçñü
4xÐ@+¹—û%øªî¿êj»$%$tÒ+/=¯™’2RCRFjgF¦^yñçÝ©ê±ˆëÆŽÑ§;3Îø’‡ƒ•c1sCU·áOU@ÎUµÉÓ<2âXþ!Ïß§Ž9ª¤ä>úî›ý-õÀ‚…
×¼{ï®ë¦Ô;«õSãf-¯—T(éTÅÀåß…’ŠŒœ‘U·²ŠŸä]XUZZª#GŽjcúfç·gQ½8VÏ]™Ê/«Üþ‡ß¼°jgF¦’ûÐØkG«SÇuÝœz‰cõÜÅ¥E @ÐâÒ" Þ#È F#È F#È F#È F#È€:v6U¿d@†ªß€É2œJJJ4áÃJèÚC;wÓ¬9÷¸ÕóWu×JÅh_Š}UzöT×_5c_„s
A†sÂã«V뫯öéÝéú`û6ýëÛoõÔÚgœËýUݵR1Ú_…boSª®ª»¾*[©–ìZ7jÆ©o§¹}q8×d8'Ìœ>Uqq—jÐЫ5`ð0]vYœî¼ýÎåÕQu×W…b_Õ’=Uë
¤šñ´™stâøñ€Ú
Ô'”qª ŠêG Q¡¨PM¨PÔ
.- ‚— õA 0A 0A 0A 0š1AöÜëÔ±Kwåæå¹=¿÷«}º¼]Ged–UMåúT®ê¢bp}®Rìk¬]•””è†q¿Rû„ÄZh•g•÷ƒÕ¶¢ªë†ñÁÙ«‰ÏT]3&È&Ü<^íãÛéÁ…‹œÏÙl6Í{`nw£zöHªÃÖU¯º¨\Ÿ«7oÞ\_ïÝí÷uGÕçÿB_ìÚYò¬ò~°ÚöÚãxbL…††j飋ôÁG;œUq_c½òÖœYÓ¯óVé×W…^OâÚÆ+}ÓõèÝW“´æ©Ÿþ`«¯ê½Þ–iÆì¹jŸ¨¤ä>zcý¯ÛöT1ØWãÊüUöÔ¯ê®R¼eÛ;4ìj]Þ®£ú
ªwÞ}ÏR_¬Vcö×>Wyyyj×±‹ó±¿108År[]Ç ¶§oÚ¢!)#uy»ŽêÖó*~rÇýP¹íþÚãí3ëÉë^RפduHèªé³æ8ÿð°·q=Ûñ±º¿ÆÞ0N/½òª$éßÿþNqmãµî¥W$•Wøù•tìÇ-¯Ï_upoû"1õUÜÓXx7més©Œ 2Iºä’6š3kºæÍP99µxér-Z¸@
6t¾Æ_¥ß@|¸c‡>þ`»V=¶BK–Љ'%ù®ÞëmÙÒå+õÍ7ÿÒÖMiz{ý>KÔ{ªH¿üUöÔ¯ê¬R|ª°PS§ÏÒ‚ûïÓ×{wkÚ”Iºû¾û-÷ÅJ5f+íóÅêøkkå1°Òö‚‚M›9[)Æh_Ößµæ‰ÇµdÙ
ýxü¸Ç6¸ò×oŸYÏcð±¶mÞ¨MiôüÓÙ>oãz¶ãcuõï×WŸîÌ”$íúìs]pÁÊÜõ™$igF¦ºtNPã/´¼>_ÕÁ}í‹@ÆÔJup×±ð6nþ*™×ô¹ÇTF™$Ý2þ×jݪ•F_÷
4@ú÷s.³Ré7n¯ÈÈHõëÛGv»]§NHò]½×Û²ôÍ›5gÖµ‰ÕÅ·ÔÌéS-·#Ð~ù«.ì_®Ç[•b[Y™l6›<¤Ó§OëÚ1£ô·ï[î‹•jÌVÚç‹•1°ÒÖÊc`¥í‘QQÚ¾u“&M¼C¡¡¡
/¯o[ä§‹•öXÝ·’4ïž¹n•¬7mÙ"Éú¸:>V×Û¿__íÌÈ”ÝnWægŸéÖ[Æ+#³üñÎŒLç1ou}¾ªƒ[ÙVÆÔJupÇX„‡‡{7_mõ×çš8÷˜Â¸
Ñ¡¡¡šÿ½1j¬î»g®Û2+•~üUè•ä,¬âþ—¼|Uïõµì—ÊÁmÚÄúo@…@úå¯
’÷~ºoUŠ6l¨µkžÐš§žÖü…+¾Ý•š1mŠ®ê\mÕ˜´Ï+c`¥•ÇÀJÛÃô;+K«Ÿ\+»Ý®++*Uûc¥=V÷tf%ëÿþ÷˜$ëãèøX]oB§Ž*++Õ7ßüK»v}®W_zAyýM}÷Ý÷Ú™‘©Ûn½% õùªne_XS+ÕÁcáoܼµÕ_ŸkâÜc
ã‚L’_x¡Û½ƒk¥_ÇŽ«\é×n·+$$Äo…^ȩ́C£èhíøà]ç%Í‚‚åçñ¹ì¦ñôÃÔºU+IÒÙ–ûk¥_VÛç«_®Ç[•âââbÅÄÄè//¯Saa‘^{ã
Mœ_¬Œ•¶V¥RsAAfΚ«õ¯¿ª„„N²Ùln?¹ŸM{¬î[©¼’u\Ü¥’Ê÷Ô¢EsIÖÇ5Ðñ±ºÞ°°0õís•ÞJ}[e¶2µju±zõì¡7Ö¿¥â’uèÐ> õ9ªƒ;޽/³²´÷«}úõM¿´´/¬Œ©kuð¸¸K%YÜ1¾ÆÍf·ym«¿>×Ä¹ÇÆ]ZôÅW¥_+zòU½×Û²Q׌ÐâeËu ;[‡åjåãþoç¨hã@ª{ÛæÙ¬Çf³éæ[«ŒÌ]Š8/BÑ
£Õ ªA•úâËÙôÓŠêl«+›Í¦2›MçE¨´´Ôùå‚b—±wÝ5Õž‡=êVÉzì˜1’¬kM~.û÷ë«_zE=ºw—$õꙤu/¾¬þýú8gV×ç«:¸•}a…¯êàŒ›¯¶úësUÏ=õ¡Âx½
2É{¥__zå«z¯·eÓ¦LÖm/WʈQ9z¬n¼á:¯ë÷T18
ÆTöµÍª¬G’¢¢¢ôàó4uÆl]Ù>A«Ÿ\«åKR_|©jûQ]mu9³fhܯnQ¯>®Þɽ4á¶ß{Ü5ÕžûkÈðºfÌuêÒ¹³&M¼]R`ãZSŸË¾¿SLJê&IêÕ³‡NºýNÜêú|U÷µ/á«:¸'ÞÆÍ_%óš8÷Ô‡
ã”q€³DuðšC ¨T¯[ œ%ªƒ×-.- ‚— õA 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0Zxml¤q³–µ± @;–¨Ö·Y+A&Iv»½¶6 ¨e!!!u¶íZ2IúñHnmn P22wÕéöù Àhudqñ‰ÜsÏ=÷Ü׃ûºæé¢fıüCÅÕ¹‘ÆÍZÊn·;/-ÆÅ'ê»ý_Tç& | |