def __init__(self, patient, chartimage, parent): BaseDialog.__init__(self, parent) self.pt = patient self.chartimage = chartimage patient_label = QtWidgets.QLabel( "%s<br /><b>%s</b>" % (_("Print the record of"), patient.name_id)) patient_label.setAlignment(QtCore.Qt.AlignCenter) self.web_view = OMWebView(self) self.web_view.loadStarted.connect(self.print_start) self.web_view.loadFinished.connect(self.print_load_result) self.insertWidget(patient_label) self.insertWidget(self.web_view) self.apply_but.setText("Print") self.enableApply() html = patientDetails.header(self.pt).replace("center", "left") html += '''<hr /> <div align="center"> <img src="%s" width="80%%" /> </div> <hr />''' % self.chartimage html += formatted_notes.notes(self.pt.notes_dict) self.web_view.setHtml(html)
class PrintRecordDialog(BaseDialog): def __init__(self, patient, chartimage, parent): BaseDialog.__init__(self, parent) self.pt = patient self.chartimage = chartimage patient_label = QtWidgets.QLabel( "%s<br /><b>%s</b>" % (_("Print the record of"), patient.name_id)) patient_label.setAlignment(QtCore.Qt.AlignCenter) self.web_view = OMWebView(self) self.web_view.loadStarted.connect(self.print_start) self.web_view.loadFinished.connect(self.print_load_result) self.insertWidget(patient_label) self.insertWidget(self.web_view) self.apply_but.setText("Print") self.enableApply() html = patientDetails.header(self.pt).replace("center", "left") html += '''<hr /> <div align="center"> <img src="%s" width="80%%" /> </div> <hr />''' % self.chartimage html += formatted_notes.notes(self.pt.notes_dict) self.web_view.setHtml(html) def print_load_result(self, result): print("Load successful = %s" % result) def print_start(self): print("Load started") def sizeHint(self): return QtCore.QSize(800, 600) def exec_(self): if BaseDialog.exec_(self): printer = QtPrintSupport.QPrinter() printer.setPaperSize(printer.A4) dialog = QtPrintSupport.QPrintDialog(printer, self.parent()) if not dialog.exec_(): return False self.web_view.print_(printer)
def __init__(self, parent=None): BaseDialog.__init__(self, parent) self.setWindowTitle(_("Advanced Names Dialog")) label = WarningLabel( _("Previous Surnames, Nicknames, and alternate " "spelling can help when searching for patients")) self.browser = OMWebView(self) self.browser.linkClicked.connect(self.link_clicked) self.insertWidget(label) self.insertWidget(self.browser) self.cancel_but.hide() self.enableApply()
def __init__(self, parent=None): BaseDialog.__init__(self, parent) self.setWindowTitle(_("Advanced Names Dialog")) label = WarningLabel(_("Previous Surnames, Nicknames, and alternate " "spelling can help when searching for patients")) self.browser = OMWebView(self) self.browser.linkClicked.connect(self.link_clicked) self.insertWidget(label) self.insertWidget(self.browser) self.cancel_but.hide() self.enableApply()
class DiaryScheduleController(QtWidgets.QStackedWidget): ''' This widget lives down the left side of the diary widget. It provides a way of switching modes for the diary. ''' BROWSE_MODE = 0 SCHEDULE_MODE = 1 BLOCKING_MODE = 2 NOTES_MODE = 3 NOT_SEARCHING = 0 SEARCHING_FORWARDS = 1 SEARCHING_BACKWARDS = 2 mode = BROWSE_MODE search_mode = NOT_SEARCHING appointment_selected = QtCore.pyqtSignal(object) patient_selected = QtCore.pyqtSignal(object) show_first_appointment = QtCore.pyqtSignal() chosen_slot_changed = QtCore.pyqtSignal() find_appt = QtCore.pyqtSignal(object) schedule_signal = QtCore.pyqtSignal(object) # from embedded pt diary widget advice_signal = QtCore.pyqtSignal(object, object) pt = None primary_slots = [] secondary_slots = [] _chosen_slot = None finding_joint_appointments = False def __init__(self, parent=None): QtWidgets.QStackedWidget.__init__(self, parent) self.diary_widget = parent self.patient_label = QtWidgets.QLabel() icon = QtGui.QIcon(":/search.png") self.get_patient_button = QtWidgets.QPushButton(icon, "") self.get_patient_button.setMaximumWidth(40) self.appt_listView = DraggableList(self) self.block_listView = DraggableList(self) self.item_delegate = ColouredItemDelegate(self) self.appointment_model = SimpleListModel(self) self.appt_listView.setModel(self.appointment_model) self.appt_listView.setItemDelegate(self.item_delegate) self.appt_listView.setSelectionModel( self.appointment_model.selection_model) self.appt_listView.setSelectionMode( QtWidgets.QListView.ContiguousSelection) block_model = BlockListModel(self) self.block_listView.setModel(block_model) icon = QtGui.QIcon(":vcalendar.png") diary_button = QtWidgets.QPushButton(icon, _("Diary")) diary_button.setToolTip(_("Open the patient's diary")) icon = QtGui.QIcon(":settings.png") settings_button = QtWidgets.QPushButton(icon, _("Options")) settings_button.setToolTip(_("Appointment Settings")) icon = QtGui.QIcon(":back.png") self.prev_appt_button = QtWidgets.QPushButton(icon, "") self.prev_appt_button.setToolTip(_("Previous appointment")) icon = QtGui.QIcon(":forward.png") self.next_appt_button = QtWidgets.QPushButton(icon, "") self.next_appt_button.setToolTip(_("Next available appointment")) icon = QtGui.QIcon(":forward.png") self.next_day_button = QtWidgets.QPushButton(icon, "") self.next_day_button.setToolTip(_("Next Day or Week")) icon = QtGui.QIcon(":back.png") self.prev_day_button = QtWidgets.QPushButton(icon, "") self.prev_day_button.setToolTip(_("Previous Day or Week")) icon = QtGui.QIcon(":first.png") self.first_appt_button = QtWidgets.QPushButton(icon, "") self.first_appt_button.setToolTip(_("First available appointment")) self.appt_controls_frame = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(self.appt_controls_frame) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) layout.addWidget(diary_button, 0, 0, 1, 2) layout.addWidget(settings_button, 0, 3, 1, 2) layout.addWidget(self.prev_day_button, 1, 0) layout.addWidget(self.prev_appt_button, 1, 1) layout.addWidget(self.first_appt_button, 1, 2) layout.addWidget(self.next_appt_button, 1, 3) layout.addWidget(self.next_day_button, 1, 4) self.appt_controls_frame.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)) self.search_criteria_webview = OMWebView(self) self.search_criteria_webview.setMinimumHeight(100) self.search_criteria_webview.setHtml( _("No appointment selected for scheduling")) # now arrange the stacked widget # page 0 - Browsing mode self.browsing_webview = OMWebView(self) self.reset_browsing_webview() self.addWidget(self.browsing_webview) # page 1 -- scheduling mode widg = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(widg) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.patient_label, 0, 0) layout.addWidget(self.get_patient_button, 0, 1) layout.addWidget(self.appt_listView, 2, 0, 1, 2) layout.addWidget(self.appt_controls_frame, 3, 0, 1, 2) layout.addWidget(self.search_criteria_webview, 4, 0, 1, 2) self.addWidget(widg) # page 2 -- blocking mode widg = QtWidgets.QWidget() layout = QtWidgets.QVBoxLayout(widg) layout.addWidget(self.block_listView) self.addWidget(widg) # page 4 -- notes mode self.notes_label = QtWidgets.QLabel(_("Select a patient to edit notes")) self.addWidget(self.notes_label) # connect signals self.get_patient_button.clicked.connect(self.find_patient) settings_button.clicked.connect(self.show_settings_dialog) self.prev_appt_button.clicked.connect(self.show_prev_appt) self.next_appt_button.clicked.connect(self.show_next_appt) self.prev_day_button.clicked.connect(self.show_prev_day) self.next_day_button.clicked.connect(self.show_next_day) self.first_appt_button.clicked.connect(self.find_first_appointment) diary_button.clicked.connect(self.show_pt_diary) self.connect_listview_signals() self.appt_listView.doubleClicked.connect(self.appointment_2x_clicked) def connect_listview_signals(self, connect=True): signal = self.appt_listView.selectionModel().selectionChanged if connect: signal.connect(self.selection_changed) else: signal.disconnect(self.selection_changed) def set_mode(self, mode): if self.mode == mode: return if mode == self.SCHEDULE_MODE: self.update_patient_label() self.enable_scheduling_buttons() else: self.clear_slots() if mode == self.BROWSE_MODE: self.reset_browsing_webview() self.mode = mode self.setCurrentIndex(mode) def set_search_mode(self, mode): assert mode in (self.NOT_SEARCHING, self.SEARCHING_FORWARDS, self.SEARCHING_BACKWARDS) LOGGER.debug("set search mode = %s", ("OFF", "FOWARDS", "BACKWARDS")[mode]) self.search_mode = mode def cancel_search_mode(self): self.set_search_mode(self.NOT_SEARCHING) def set_search_future(self): self.set_search_mode(self.SEARCHING_FORWARDS) def set_search_past(self): self.set_search_mode(self.SEARCHING_BACKWARDS) @property def searching_future(self): return self.search_mode == self.SEARCHING_FORWARDS @property def searching_past(self): return self.search_mode == self.SEARCHING_BACKWARDS def set_patient(self, pt): self.clear() self.pt = pt if pt is not None: self.appointment_model.load_from_database(self.pt) self.patient_selected.emit(self.pt) self.enable_scheduling_buttons() def get_data(self): ''' loads the appointment model from database, or clears if patient is None ''' LOGGER.debug("schedule control get-data") if self.pt is None: self.clear() return self.connect_listview_signals(False) self.appointment_model.load_from_database(self.pt) self.connect_listview_signals() @property def patient_text(self): if self.pt: return "%s %s (%s)" % ( self.pt.fname, self.pt.sname, self.pt.serialno) else: return _("No patient Selected") @property def serialno(self): if self.pt: return self.pt.serialno return None def find_patient(self): ''' search and load a patient. ''' dl = FindPatientDialog(self) if dl.exec_(): self.clear() pt = BriefPatient(dl.chosen_sno) self.set_patient(pt) self.diary_widget.set_date(QtCore.QDate.currentDate()) self.update_patient_label() def update_patient_label(self): self.patient_label.setText(self.patient_text) @property def selected_appointment(self): ''' the appointment currently selected by user ''' return self.appointment_model.currentAppt @property def secondary_appointment(self): ''' the appointment jointly selected by user ''' return self.appointment_model.secondaryAppt @property def app1_length(self): try: return self.appointment_model.currentAppt.length except AttributeError: return 0 @property def app1_is_scheduled(self): try: return not self.appointment_model.currentAppt.unscheduled except AttributeError: LOGGER.debug("app1_is_scheduled") return False @property def app2_length(self): try: return self.appointment_model.secondaryAppt.length except AttributeError: return 0 @property def app2_is_scheduled(self): try: return not self.appointment_model.secondaryAppt.unscheduled except AttributeError: LOGGER.debug("app2_is_scheduled") return False @classmethod def ignore_emergency_spaces(self): return ApptSettingsDialog.ignore_emergency_spaces def set_selection_mode(self, mode): assert mode in ( self.CLINICIAN_ANY, self.CLINICIAN_ANY_DENT, self.CLINICIAN_ANY_HYG, self.CLINICIAN_SELECTED), "selection mode misunderstood" self.dent_selection_mode = mode @property def selectedClinicians(self): return self.appointment_model.selectedClinicians def _appt_clinicians(self, appt): ''' use the selected appointment and the chosen settings to see who could perform this treatment. ''' if appt.dent in localsettings.activedent_ixs: if ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_DENT: return localsettings.activedent_ixs if ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_ANY: return \ localsettings.activedent_ixs + localsettings.activehyg_ixs elif appt.dent in localsettings.activehyg_ixs: if ApptSettingsDialog.hygienist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_HYG: return localsettings.activehyg_ixs return (appt.dent, ) @property def appt1_clinicians(self): ''' what clinicians could provide the treatment in appointment 1? ''' appt = self.appointment_model.currentAppt if appt: return self._appt_clinicians(appt) return () @property def appt2_clinicians(self): ''' what clinicians could provide the treatment in appointment 2? ''' appt = self.appointment_model.secondaryAppt if appt: return self._appt_clinicians(appt) return () @property def involvedClinicians(self): return self.appointment_model.involvedClinicians def sizeHint(self): return QtCore.QSize(180, 400) def update_appt_selection(self, appts): ''' sync with the "patient diary" via signal/slot ''' LOGGER.debug( "updating schedule controller appointments, appts='%s'", appts) for appt in appts: if appt.serialno != self.serialno: return self.connect_listview_signals(False) self.appointment_model.set_selected_appointments(appts) self.connect_listview_signals() def update_selected_appointment(self, appt): self.primary_slots = [] self._chosen_slot = None self.enable_scheduling_buttons() def selection_changed(self, selection): LOGGER.debug("ScheduleControl.selection_changed") self.update_search_criteria_webview() self.enable_scheduling_buttons() try: app = self.appointment_model.data(selection.indexes()[-1], QtCore.Qt.UserRole) except IndexError: app = self.appointment_model.currentAppt self.appointment_selected.emit(app) def appointment_2x_clicked(self, index): LOGGER.debug("ScheduleControl.appointment_clicked") self.show_first_appointment.emit() def clear(self): self.reset_browsing_webview() self.appointment_model.clear() self.reset() def reset(self): self.reset_browsing_webview() self.primary_slots = [] self.secondary_slots = [] self._chosen_slot = None self.cancel_search_mode() self.finding_joint_appointments = False def show_settings_dialog(self): ''' show the settings dialog ''' LOGGER.info("showing the settings dialog") dl = ApptSettingsDialog(self) if dl.exec_(): self.selection_changed( self.appt_listView.selectionModel().selection()) @property def _chosen_slot_no(self): try: return self.primary_slots.index(self._chosen_slot) except ValueError: return 0 def show_next_appt(self): self.set_search_future() try: self._chosen_slot = self.primary_slots[self._chosen_slot_no + 1] self.chosen_slot_changed.emit() except IndexError: self.show_next_day() def show_prev_appt(self): self.set_search_past() try: i = self._chosen_slot_no - 1 if i < 0: raise IndexError self._chosen_slot = self.primary_slots[i] self.chosen_slot_changed.emit() except IndexError: self.show_prev_day() def show_next_day(self): ''' catches the signal to make a big jump forwards. will jump a week if in week view ''' self._chosen_slot = None self.set_search_future() self.diary_widget.step_date(True) def show_prev_day(self): ''' catches the signal to make a big jump backwords. will jump a week if in week view ''' self._chosen_slot = None self.set_search_past() self.diary_widget.step_date(False) @property def all_slots(self): for slot in self.primary_slots: yield slot for slot in self.secondary_slots: yield slot def clear_slots(self): # self.reset_browsing_webview() self._chosen_slot = None self.primary_slots = [] self.secondary_slots = [] def reset_browsing_webview(self): self.browsing_webview.setHtml("") def set_primary_slots(self, slots): self.primary_slots = [] for slot in sorted(slots): if (slot.dent in self.appt1_clinicians and slot.day_no not in self.excluded_days): self.primary_slots.append(slot) def set_secondary_slots(self, slots): LOGGER.debug("filtering secondary slots %s", slots) self.secondary_slots = [] for slot in sorted(slots): slot.is_primary = False if (slot.dent in self.appt2_clinicians and slot.day_no not in self.excluded_days): self.secondary_slots.append(slot) def set_slots_from_day_app_data(self, app_data): if self.app1_is_scheduled: self.set_primary_slots([]) else: self.set_primary_slots( app_data.slots(self.app1_length, self.ignore_emergency_spaces())) app2_slots = [] if self.is_searching_for_double_appointments and \ not self.app2_is_scheduled: self.set_secondary_slots( app_data.slots(self.app2_length, self.ignore_emergency_spaces(), busy_serialno=self.serialno)) app1_slots = set([]) if self.app1_is_scheduled: iterset = [self.appointment_model.currentAppt.to_freeslot()] else: iterset = self.primary_slots for app1_slot in iterset: for app2_slot in self.secondary_slots: wait = app1_slot.wait_time(self.app1_length, self.app2_length, app2_slot) if wait is not None and wait <= MAX_WAIT: app2_slots.append(app2_slot) app1_slots.add(app1_slot) if not self.app1_is_scheduled: self.set_primary_slots(app1_slots) self.set_secondary_slots(app2_slots) def get_weekview_slots(self, weekdates): ''' calculate available slots for weekdates (list of QDates) ''' today = QtCore.QDate.currentDate() startday = today if today in weekdates else weekdates[0] # monday sunday = weekdates[6] # sunday # check for suitable apts in the selected WEEK! all_slots = appointments.future_slots( startday.toPyDate(), sunday.toPyDate(), self.appt1_clinicians, busy_serialno=self.serialno, override_emergencies=self.ignore_emergency_spaces()) if self.app1_is_scheduled: self.set_primary_slots([]) else: self.set_primary_slots( appointments.getLengthySlots(all_slots, self.app1_length)) app2_slots = [] if self.is_searching_for_double_appointments and \ not self.app2_is_scheduled: all_slots = appointments.future_slots( startday.toPyDate(), sunday.toPyDate(), self.appt2_clinicians, busy_serialno=self.serialno, override_emergencies=self.ignore_emergency_spaces()) self.set_secondary_slots( appointments.getLengthySlots(all_slots, self.app2_length) ) app1_slots = set([]) if self.app1_is_scheduled: iterset = [self.appointment_model.currentAppt.to_freeslot()] else: iterset = self.primary_slots for app1_slot in iterset: for app2_slot in self.secondary_slots: wait = app1_slot.wait_time(self.app1_length, self.app2_length, app2_slot) if wait is not None and wait <= MAX_WAIT: app2_slots.append(app2_slot) app1_slots.add(app1_slot) if not self.app1_is_scheduled: self.set_primary_slots(app1_slots) self.set_secondary_slots(app2_slots) @property def chosen_2nd_slots(self): if not (self.appointment_model.currentAppt is None or self.appointment_model.secondaryAppt is None): for app2_slot in self.secondary_slots: wait = self.chosen_slot.wait_time(self.app1_length, self.app2_length, app2_slot) if wait is not None and wait <= MAX_WAIT: yield app2_slot else: LOGGER.debug("no appointments selected") @property def last_appt_date(self): ''' returns the latest date of patient's appointments, or today's date if none found ''' last_d = QtCore.QDate.currentDate().toPyDate() for appt in self.appointment_model.scheduledList: if appt.date > last_d: last_d = appt.date return last_d @property def is_searching(self): ''' are we looking for a slot? ''' app1 = self.appointment_model.currentAppt if app1 is not None and app1.unscheduled: return True app2 = self.appointment_model.secondaryAppt if app2 is not None and app2.unscheduled: return True return False @property def is_searching_for_double_appointments(self): return self.appointment_model.secondaryAppt is not None @property def search_again(self): ''' this determines whether it is worth continuing (on a different date) ''' LOGGER.debug("search again is_searching = %s", self.is_searching) LOGGER.debug("search again forwards = %s", self.search_mode == self.SEARCHING_FORWARDS) LOGGER.debug("search again backwards = %s", self.search_mode == self.SEARCHING_BACKWARDS) LOGGER.debug("search again n_clinicians = %s", len(self.selectedClinicians)) LOGGER.debug("search again still have slots = %s", len(self.primary_slots) == 0) search_again = (self.is_searching and self.search_mode != self.NOT_SEARCHING and len(self.selectedClinicians) > 0 and len(self.primary_slots) == 0) LOGGER.debug("therefore searce again = %s", search_again) return search_again def find_first_appointment(self): LOGGER.debug("find_first_appointment") self.show_first_appointment.emit() @property def chosen_slot(self): if self.primary_slots == []: if self.app1_is_scheduled: return self.appointment_model.currentAppt.to_freeslot() return None if self._chosen_slot is None: if self.searching_past: self._chosen_slot = self.primary_slots[-1] else: self._chosen_slot = self.primary_slots[0] return self._chosen_slot def set_chosen_slot(self, slot): self._chosen_slot = slot def set_chosen_2nd_slot(self, slot): ''' user has clicked on a secondary slot - we need to switch the appointments over! ''' LOGGER.debug("set_chosen_2nd_slot %s", slot) model = self.appointment_model.selection_model selection = model.selection() selection.swap(0, 1) model.select(selection, model.ClearAndSelect) self.selection_changed( self.appointment_model.selection_model.selection()) self.appointment_model.selection_model.emitSelectionChanged( selection, QtCore.QItemSelection()) slot.is_primary = True self.set_chosen_slot(slot) self.chosen_slot_changed.emit() @property def excluded_days(self): return ApptSettingsDialog.excluded_days def show_pt_diary(self): if self.pt is None: QtWidgets.QMessageBox.information(self, _("error"), _("No patient selected")) return def _find_appt(appt): dl.accept() self.find_appt.emit(appt) def _start_scheduling(custom): ''' if custom, the dialog will be raised giving day of week options etc ''' dl.accept() self.get_data() self.appointment_model.selection_model.reset() appts = pt_diary_widget.selected_appointments self.update_appt_selection(appts) QtCore.QTimer.singleShot(100, partial(self.schedule_signal.emit, custom) ) pt_diary_widget = PtDiaryWidget() pt_diary_widget.find_appt.connect(_find_appt) pt_diary_widget.start_scheduling_signal.connect(_start_scheduling) pt_diary_widget.set_patient(self.pt) dl = BaseDialog(self, remove_stretch=True) dl.insertWidget(pt_diary_widget) dl.cancel_but.setText(_("OK")) dl.apply_but.hide() dl.setMinimumWidth(800) dl.setMaximumWidth(800) if not dl.exec_(): # dialog is only accepted by subroutines above self.appointment_model.load_from_database(self.pt) self.enable_scheduling_buttons() def enable_scheduling_buttons(self): enabled = self.is_searching for but in (self.next_appt_button, self.next_day_button, self.prev_appt_button, self.prev_day_button, self.first_appt_button): but.setEnabled(enabled) self.update_search_criteria_webview() def begin_make_appointment(self, custom): ''' this is when an external widget calls for us to start the process of starting an appointment if custom is True, then the ApptSettingsDialog is shown. ''' LOGGER.debug("begin_make_appointment custom = %s", custom) self.set_mode(self.SCHEDULE_MODE) self.enable_scheduling_buttons() LOGGER.debug("checking appointment settings %s", ApptSettingsDialog.is_default_settings()) if custom == True: self.show_settings_dialog() elif not ApptSettingsDialog.is_default_settings(): dl = ApptSettingsResetDialog(self) if dl.exec_(): if dl.show_settings_dialog: self.show_settings_dialog() else: ApptSettingsDialog.reset() def check_schedule_status(self, automatic): ''' this is called by the diary widget when the books have been laid out whilst in schedule mode. Inform the user ''' assert self.mode == self.SCHEDULE_MODE, "not in schedule mode" LOGGER.debug("=" * 40) LOGGER.debug( "check_schedule_status %s", self.diary_widget.selected_date()) if automatic: # this has been called by a timer update to the diary, # and therefore NOT user interaction LOGGER.debug("automatic call to check_schedule_status... ignoring") return try: date_ = self.diary_widget.selected_date().toPyDate() except AttributeError: # self.diary_widget is None? LOGGER.debug("date check error", exc_info=1) date_ = localsettings.currentDay() if not self.appointment_model.selectedAppts: LOGGER.debug("schedule status - no selected appointments") self.advice_signal.emit( _("Please select an appointment to begin scheduling"), 0) elif self.app1_is_scheduled: LOGGER.debug("schedule status - appt 1 is scheduled") if self.appointment_model.secondaryAppt is None: self.advice_signal.emit( _("appointment is already scheduled"), 0) elif self.app2_is_scheduled: LOGGER.debug("schedule status - appt 2 also scheduled") self.advice_signal.emit(_("Joint appointment Scheduled"), 0) elif not list(self.chosen_2nd_slots): LOGGER.debug("schedule status - no slots for appt 2") self.advice_signal.emit( _("Joint appointment is not possible with the " "chosen primary appointment"), 1) elif self.search_again: LOGGER.debug("schedule status - search again") if date_ > localsettings.BOOKEND: LOGGER.debug( "schedule status - search again has reached bookend") self.advice_signal.emit( '''<b>%s<br />%s</b><hr /><em>(%s)</em> <ul> <li>%s</li><li>%s</li><li>%s</li><li>%s</li> </ul>''' % ( _("This date is beyond the diary limit."), _("Please search again with different criteria."), _("for instance..."), _("no excluded days"), _("ignore emergencies"), _("add or view more clinicians"), _("or you have requested an impossible appointment!")), 1) self.cancel_search_mode() self.diary_widget.set_date(localsettings.currentDay()) elif date_ < localsettings.currentDay(): LOGGER.debug( "schedule status - search again has reached the past") self.advice_signal.emit( _("You can't schedule an appointment in the past"), 1) self.diary_widget.set_date(localsettings.currentDay()) else: LOGGER.debug("schedule status - calling step date") self.diary_widget.step_date(self.searching_future) elif date_ > localsettings.BOOKEND: LOGGER.debug("schedule status - beyond bookend") self.advice_signal.emit( _("This date is beyond the diary limit."), 1) elif date_ < localsettings.currentDay(): LOGGER.debug("schedule status - in the past!") self.advice_signal.emit( _("You can't schedule an appointment in the past"), 1) self.diary_widget.set_date(localsettings.currentDay()) elif self.chosen_slot is None and not list(self.chosen_2nd_slots): LOGGER.debug("schedule status - no 2nd slots") if self.diary_widget: if self.diary_widget.viewing_week: message = "%s %s" % ( _("in this week"), self.diary_widget.selected_date().weekNumber()) else: message = "%s (%s)" % ( _("on this day"), localsettings.formatDate( self.diary_widget.selected_date().toPyDate()) ) else: message = "" # should only happen if __name__ == "__main__" message = "%s %s" % (_("No Slots Found"), message) self.advice_signal.emit(message, 0) else: LOGGER.debug("schedule status - nothing to do") LOGGER.debug("=" * 40) @property def _dentist_message(self): if not self.appointment_model.dentists_involved: return "" if ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_SELECTED: return _("Specified Dentist only") elif ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_DENT: return _("Any Dentist") else: # CLINICIAN_ANY return _("Any Clinician") @property def _hygienist_message(self): if not self.appointment_model.hygienists_involved: return "" if ApptSettingsDialog.hygienist_policy == \ ApptSettingsDialog.CLINICIAN_SELECTED: return _("Specified Hygienist only for hyg appts") if ApptSettingsDialog.hygienist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_HYG: return _("Any Hygienist for hyg appts") else: # CLINICIAN_ANY return _("Any Clinician for hyg appts") @property def _joint_message(self): if self.is_searching_for_double_appointments: return _("Joint Appointments") return "" @property def _emergency_message(self): if self.ignore_emergency_spaces(): return "%s" % _("Overwrite Emergencies") return "" @property def _day_message(self): if self.excluded_days == []: return _("Any Day") days = [] for i, day in enumerate( (_("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")), 1): if i not in self.excluded_days: days.append(day) return ", ".join(days) def update_search_criteria_webview(self): if self.appointment_model.currentAppt is None: html = _("No Appointment Selected") else: html = SETTINGS_HTML % ( _("Search Settings"), '</li><li class="search-settings-item">'. join( [s for s in ( self._dentist_message, self._hygienist_message, self._joint_message, self._emergency_message, self._day_message) if s != ""]) ) self.search_criteria_webview.setHtml(html) def update_highlighted_appointment(self): ''' the diary widget selected appointment has changed. ''' app = self.diary_widget.highlighted_appointment LOGGER.debug("appointment highlighted %s", app) if app is None: self.reset_browsing_webview() return if self.mode == self.NOTES_MODE: self.notes_label.setText( "<h3>%s</h3>%s<br />%s" % ( _("View/edit today's notes for "), app.name, app.serialno ) ) return self.notes_label.setText("") if self.mode != self.BROWSE_MODE: return feedback = FEEDBACK % ( app.name, app.serialno, localsettings.readableDate( self.diary_widget.selected_date().toPyDate()), "%02d:%02d" % (app.start // 100, app.start % 100), "%02d:%02d" % (app.end // 100, app.end % 100), '</li><li class="trt">'.join( [val for val in (app.trt1, app.trt2, app.trt3) if val != ""]) ) if app.memo != "": feedback += "<hr />%s<br /><i>%s</i>" % (_("Memo"), app.memo) try: datestamp = app.timestamp.date() feedback += \ "<hr />%s<br />%s (%s %s)" % ( _("Made"), localsettings.formatDate(datestamp), _("at"), localsettings.pyTimeToHumantime( app.timestamp)) except AttributeError: pass if app.mh_form_check_date or app.mh_form_required: feedback += "<hr />" if app.mh_form_check_date: feedback += "%s %s<br />" % ( _("last mh form"), localsettings.formatDate( app.mh_form_check_date) ) if app.mh_form_required: feedback += "%s" % _("MH CHECK REQUIRED") feedback = "%s<body></html>" % feedback self.browsing_webview.setHtml(feedback)
def __init__(self, parent=None): QtWidgets.QStackedWidget.__init__(self, parent) self.diary_widget = parent self.patient_label = QtWidgets.QLabel() icon = QtGui.QIcon(":/search.png") self.get_patient_button = QtWidgets.QPushButton(icon, "") self.get_patient_button.setMaximumWidth(40) self.appt_listView = DraggableList(self) self.block_listView = DraggableList(self) self.item_delegate = ColouredItemDelegate(self) self.appointment_model = SimpleListModel(self) self.appt_listView.setModel(self.appointment_model) self.appt_listView.setItemDelegate(self.item_delegate) self.appt_listView.setSelectionModel( self.appointment_model.selection_model) self.appt_listView.setSelectionMode( QtWidgets.QListView.ContiguousSelection) block_model = BlockListModel(self) self.block_listView.setModel(block_model) icon = QtGui.QIcon(":vcalendar.png") diary_button = QtWidgets.QPushButton(icon, _("Diary")) diary_button.setToolTip(_("Open the patient's diary")) icon = QtGui.QIcon(":settings.png") settings_button = QtWidgets.QPushButton(icon, _("Options")) settings_button.setToolTip(_("Appointment Settings")) icon = QtGui.QIcon(":back.png") self.prev_appt_button = QtWidgets.QPushButton(icon, "") self.prev_appt_button.setToolTip(_("Previous appointment")) icon = QtGui.QIcon(":forward.png") self.next_appt_button = QtWidgets.QPushButton(icon, "") self.next_appt_button.setToolTip(_("Next available appointment")) icon = QtGui.QIcon(":forward.png") self.next_day_button = QtWidgets.QPushButton(icon, "") self.next_day_button.setToolTip(_("Next Day or Week")) icon = QtGui.QIcon(":back.png") self.prev_day_button = QtWidgets.QPushButton(icon, "") self.prev_day_button.setToolTip(_("Previous Day or Week")) icon = QtGui.QIcon(":first.png") self.first_appt_button = QtWidgets.QPushButton(icon, "") self.first_appt_button.setToolTip(_("First available appointment")) self.appt_controls_frame = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(self.appt_controls_frame) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) layout.addWidget(diary_button, 0, 0, 1, 2) layout.addWidget(settings_button, 0, 3, 1, 2) layout.addWidget(self.prev_day_button, 1, 0) layout.addWidget(self.prev_appt_button, 1, 1) layout.addWidget(self.first_appt_button, 1, 2) layout.addWidget(self.next_appt_button, 1, 3) layout.addWidget(self.next_day_button, 1, 4) self.appt_controls_frame.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)) self.search_criteria_webview = OMWebView(self) self.search_criteria_webview.setMinimumHeight(100) self.search_criteria_webview.setHtml( _("No appointment selected for scheduling")) # now arrange the stacked widget # page 0 - Browsing mode self.browsing_webview = OMWebView(self) self.reset_browsing_webview() self.addWidget(self.browsing_webview) # page 1 -- scheduling mode widg = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(widg) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.patient_label, 0, 0) layout.addWidget(self.get_patient_button, 0, 1) layout.addWidget(self.appt_listView, 2, 0, 1, 2) layout.addWidget(self.appt_controls_frame, 3, 0, 1, 2) layout.addWidget(self.search_criteria_webview, 4, 0, 1, 2) self.addWidget(widg) # page 2 -- blocking mode widg = QtWidgets.QWidget() layout = QtWidgets.QVBoxLayout(widg) layout.addWidget(self.block_listView) self.addWidget(widg) # page 4 -- notes mode self.notes_label = QtWidgets.QLabel(_("Select a patient to edit notes")) self.addWidget(self.notes_label) # connect signals self.get_patient_button.clicked.connect(self.find_patient) settings_button.clicked.connect(self.show_settings_dialog) self.prev_appt_button.clicked.connect(self.show_prev_appt) self.next_appt_button.clicked.connect(self.show_next_appt) self.prev_day_button.clicked.connect(self.show_prev_day) self.next_day_button.clicked.connect(self.show_next_day) self.first_appt_button.clicked.connect(self.find_first_appointment) diary_button.clicked.connect(self.show_pt_diary) self.connect_listview_signals() self.appt_listView.doubleClicked.connect(self.appointment_2x_clicked)
class AdvancedNamesDialog(BaseDialog): pt = None pseudonyms = [] def __init__(self, parent=None): BaseDialog.__init__(self, parent) self.setWindowTitle(_("Advanced Names Dialog")) label = WarningLabel(_("Previous Surnames, Nicknames, and alternate " "spelling can help when searching for patients")) self.browser = OMWebView(self) self.browser.linkClicked.connect(self.link_clicked) self.insertWidget(label) self.insertWidget(self.browser) self.cancel_but.hide() self.enableApply() def link_clicked(self, url): url_text = url.toString() if url_text == "om://add_psn": self.add_previous_surname() elif url_text == "om://add_alt": self.add_alt_name() else: m = re.match(r"om://edit_name_(\d+)", url_text) if m: ix = int(m.groups()[0]) self.edit_name(ix) def add_previous_surname(self, psn=""): LOGGER.info("add a surname") if psn == "": message = _("Please enter a previous surname") else: message = "%s '%s' %s" % (_("Save"), psn, _("as a previous surname?")) surname, result = \ QtWidgets.QInputDialog.getText(self, _("Input required"), message, QtWidgets.QLineEdit.Normal, psn) if result: LOGGER.info("adding %s as a previous surname", surname) db = connect() cursor = db.cursor() cursor.execute(INSERT_PSN_QUERY, (self.pt.serialno, surname.upper())) cursor.close() self.set_patient(self.pt) return True def add_alt_name(self): LOGGER.info("add an alternative name") dl = AltNameEntryDialog(self) if dl.exec_(): fname = dl.fname_le.text() sname = dl.sname_le.text() comment = dl.comment_le.text() db = connect() cursor = db.cursor() cursor.execute(INSERT_ALT_QUERY, (self.pt.serialno, None if not fname else fname, None if not sname else sname, comment)) cursor.close() self.set_patient(self.pt) def edit_name(self, ix): LOGGER.info("edit name %s", ix) for pseudonym in self.pseudonyms: if pseudonym.ix == ix: break dl = AltNameEntryDialog(self) dl.label.setText(_("Edit this name (or leave both name fields blank " "to delete a reference to this name)")) dl.fname_le.setText(pseudonym.fname) dl.sname_le.setText(pseudonym.sname) dl.comment_le.setText(pseudonym.comment) dl.enableApply(False) if dl.exec_(): fname = dl.fname_le.text() sname = dl.sname_le.text() comment = dl.comment_le.text() db = connect() cursor = db.cursor() if sname == "" and fname =="": cursor.execute(DELETE_QUERY, (ix,)) else: cursor.execute(UPDATE_ALT_QUERY, (None if not fname else fname, None if not sname else sname, comment, ix)) cursor.close() self.set_patient(self.pt) def set_patient(self, pt): ''' pass a patient object to set the serialnumber and name fields. ''' self.pt = pt db = connect() cursor = db.cursor() cursor.execute(PSEUDONYMS_QUERY, (pt.serialno,)) alts = [] for row in cursor.fetchall(): pseudonym = Pseudonym(*row) alts.append(pseudonym) self.pseudonyms = alts previous = '</li><li>'.join( [p.html() for p in alts if p.comment=="previous surname"] + ['<a href="om://add_psn">%s</a>' % _("Add New")]) alts = '</li><li>'.join( [p.html() for p in alts if p.comment!="previous surname"] + ['<a href="om://add_alt">%s</a>' % _("Add New")]) self.browser.setHtml(HTML % (self.fname, self.sname, previous, alts)) self.browser.delegate_links() @property def sname(self): try: return self.pt.sname except AttributeError: pass @property def fname(self): try: return self.pt.fname except AttributeError: pass def check_save_previous_surname(self, surname): self.show() if self.add_previous_surname(surname): self.exec_() def sizeHint(self): return QtCore.QSize(400, 500)
class DiaryScheduleController(QtWidgets.QStackedWidget): ''' This widget lives down the left side of the diary widget. It provides a way of switching modes for the diary. ''' BROWSE_MODE = 0 SCHEDULE_MODE = 1 BLOCKING_MODE = 2 NOTES_MODE = 3 NOT_SEARCHING = 0 SEARCHING_FORWARDS = 1 SEARCHING_BACKWARDS = 2 mode = BROWSE_MODE search_mode = NOT_SEARCHING appointment_selected = QtCore.pyqtSignal(object) patient_selected = QtCore.pyqtSignal(object) show_first_appointment = QtCore.pyqtSignal() chosen_slot_changed = QtCore.pyqtSignal() find_appt = QtCore.pyqtSignal(object) schedule_signal = QtCore.pyqtSignal( object) # from embedded pt diary widget advice_signal = QtCore.pyqtSignal(object, object) pt = None primary_slots = [] secondary_slots = [] _chosen_slot = None finding_joint_appointments = False def __init__(self, parent=None): QtWidgets.QStackedWidget.__init__(self, parent) self.diary_widget = parent self.patient_label = QtWidgets.QLabel() icon = QtGui.QIcon(":/search.png") self.get_patient_button = QtWidgets.QPushButton(icon, "") self.get_patient_button.setMaximumWidth(40) self.appt_listView = DraggableList(self) self.block_listView = DraggableList(self) self.item_delegate = ColouredItemDelegate(self) self.appointment_model = SimpleListModel(self) self.appt_listView.setModel(self.appointment_model) self.appt_listView.setItemDelegate(self.item_delegate) self.appt_listView.setSelectionModel( self.appointment_model.selection_model) self.appt_listView.setSelectionMode( QtWidgets.QListView.ContiguousSelection) block_model = BlockListModel(self) self.block_listView.setModel(block_model) icon = QtGui.QIcon(":vcalendar.png") diary_button = QtWidgets.QPushButton(icon, _("Diary")) diary_button.setToolTip(_("Open the patient's diary")) icon = QtGui.QIcon(":settings.png") settings_button = QtWidgets.QPushButton(icon, _("Options")) settings_button.setToolTip(_("Appointment Settings")) icon = QtGui.QIcon(":back.png") self.prev_appt_button = QtWidgets.QPushButton(icon, "") self.prev_appt_button.setToolTip(_("Previous appointment")) icon = QtGui.QIcon(":forward.png") self.next_appt_button = QtWidgets.QPushButton(icon, "") self.next_appt_button.setToolTip(_("Next available appointment")) icon = QtGui.QIcon(":forward.png") self.next_day_button = QtWidgets.QPushButton(icon, "") self.next_day_button.setToolTip(_("Next Day or Week")) icon = QtGui.QIcon(":back.png") self.prev_day_button = QtWidgets.QPushButton(icon, "") self.prev_day_button.setToolTip(_("Previous Day or Week")) icon = QtGui.QIcon(":first.png") self.first_appt_button = QtWidgets.QPushButton(icon, "") self.first_appt_button.setToolTip(_("First available appointment")) self.appt_controls_frame = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(self.appt_controls_frame) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) layout.addWidget(diary_button, 0, 0, 1, 2) layout.addWidget(settings_button, 0, 3, 1, 2) layout.addWidget(self.prev_day_button, 1, 0) layout.addWidget(self.prev_appt_button, 1, 1) layout.addWidget(self.first_appt_button, 1, 2) layout.addWidget(self.next_appt_button, 1, 3) layout.addWidget(self.next_day_button, 1, 4) self.appt_controls_frame.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)) self.search_criteria_webview = OMWebView(self) self.search_criteria_webview.setMinimumHeight(100) self.search_criteria_webview.setHtml( _("No appointment selected for scheduling")) # now arrange the stacked widget # page 0 - Browsing mode self.browsing_webview = OMWebView(self) self.reset_browsing_webview() self.addWidget(self.browsing_webview) # page 1 -- scheduling mode widg = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(widg) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.patient_label, 0, 0) layout.addWidget(self.get_patient_button, 0, 1) layout.addWidget(self.appt_listView, 2, 0, 1, 2) layout.addWidget(self.appt_controls_frame, 3, 0, 1, 2) layout.addWidget(self.search_criteria_webview, 4, 0, 1, 2) self.addWidget(widg) # page 2 -- blocking mode widg = QtWidgets.QWidget() layout = QtWidgets.QVBoxLayout(widg) layout.addWidget(self.block_listView) self.addWidget(widg) # page 4 -- notes mode self.notes_label = QtWidgets.QLabel( _("Select a patient to edit notes")) self.addWidget(self.notes_label) # connect signals self.get_patient_button.clicked.connect(self.find_patient) settings_button.clicked.connect(self.show_settings_dialog) self.prev_appt_button.clicked.connect(self.show_prev_appt) self.next_appt_button.clicked.connect(self.show_next_appt) self.prev_day_button.clicked.connect(self.show_prev_day) self.next_day_button.clicked.connect(self.show_next_day) self.first_appt_button.clicked.connect(self.find_first_appointment) diary_button.clicked.connect(self.show_pt_diary) self.connect_listview_signals() self.appt_listView.doubleClicked.connect(self.appointment_2x_clicked) def connect_listview_signals(self, connect=True): signal = self.appt_listView.selectionModel().selectionChanged if connect: signal.connect(self.selection_changed) else: signal.disconnect(self.selection_changed) def set_mode(self, mode): if self.mode == mode: return if mode == self.SCHEDULE_MODE: self.update_patient_label() self.enable_scheduling_buttons() else: self.clear_slots() if mode == self.BROWSE_MODE: self.reset_browsing_webview() self.mode = mode self.setCurrentIndex(mode) def set_search_mode(self, mode): assert mode in (self.NOT_SEARCHING, self.SEARCHING_FORWARDS, self.SEARCHING_BACKWARDS) LOGGER.debug("set search mode = %s", ("OFF", "FOWARDS", "BACKWARDS")[mode]) self.search_mode = mode def cancel_search_mode(self): self.set_search_mode(self.NOT_SEARCHING) def set_search_future(self): self.set_search_mode(self.SEARCHING_FORWARDS) def set_search_past(self): self.set_search_mode(self.SEARCHING_BACKWARDS) @property def searching_future(self): return self.search_mode == self.SEARCHING_FORWARDS @property def searching_past(self): return self.search_mode == self.SEARCHING_BACKWARDS def set_patient(self, pt): self.clear() self.pt = pt if pt is not None: self.appointment_model.load_from_database(self.pt) self.patient_selected.emit(self.pt) self.enable_scheduling_buttons() def get_data(self): ''' loads the appointment model from database, or clears if patient is None ''' LOGGER.debug("schedule control get-data") if self.pt is None: self.clear() return self.connect_listview_signals(False) self.appointment_model.load_from_database(self.pt) self.connect_listview_signals() @property def patient_text(self): if self.pt: return "%s %s (%s)" % (self.pt.fname, self.pt.sname, self.pt.serialno) else: return _("No patient Selected") @property def serialno(self): if self.pt: return self.pt.serialno return None def find_patient(self): ''' search and load a patient. ''' dl = FindPatientDialog(self) if dl.exec_(): self.clear() pt = BriefPatient(dl.chosen_sno) self.set_patient(pt) self.diary_widget.set_date(QtCore.QDate.currentDate()) self.update_patient_label() def update_patient_label(self): self.patient_label.setText(self.patient_text) @property def selected_appointment(self): ''' the appointment currently selected by user ''' return self.appointment_model.currentAppt @property def secondary_appointment(self): ''' the appointment jointly selected by user ''' return self.appointment_model.secondaryAppt @property def app1_length(self): try: return self.appointment_model.currentAppt.length except AttributeError: return 0 @property def app1_is_scheduled(self): try: return not self.appointment_model.currentAppt.unscheduled except AttributeError: LOGGER.debug("app1_is_scheduled") return False @property def app2_length(self): try: return self.appointment_model.secondaryAppt.length except AttributeError: return 0 @property def app2_is_scheduled(self): try: return not self.appointment_model.secondaryAppt.unscheduled except AttributeError: LOGGER.debug("app2_is_scheduled") return False @classmethod def ignore_emergency_spaces(self): return ApptSettingsDialog.ignore_emergency_spaces def set_selection_mode(self, mode): assert mode in ( self.CLINICIAN_ANY, self.CLINICIAN_ANY_DENT, self.CLINICIAN_ANY_HYG, self.CLINICIAN_SELECTED), "selection mode misunderstood" self.dent_selection_mode = mode @property def selectedClinicians(self): return self.appointment_model.selectedClinicians def _appt_clinicians(self, appt): ''' use the selected appointment and the chosen settings to see who could perform this treatment. ''' if appt.dent in localsettings.activedent_ixs: if ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_DENT: return localsettings.activedent_ixs if ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_ANY: return \ localsettings.activedent_ixs + localsettings.activehyg_ixs elif appt.dent in localsettings.activehyg_ixs: if ApptSettingsDialog.hygienist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_HYG: return localsettings.activehyg_ixs return (appt.dent, ) @property def appt1_clinicians(self): ''' what clinicians could provide the treatment in appointment 1? ''' appt = self.appointment_model.currentAppt if appt: return self._appt_clinicians(appt) return () @property def appt2_clinicians(self): ''' what clinicians could provide the treatment in appointment 2? ''' appt = self.appointment_model.secondaryAppt if appt: return self._appt_clinicians(appt) return () @property def involvedClinicians(self): return self.appointment_model.involvedClinicians def sizeHint(self): return QtCore.QSize(180, 400) def update_appt_selection(self, appts): ''' sync with the "patient diary" via signal/slot ''' LOGGER.debug("updating schedule controller appointments, appts='%s'", appts) for appt in appts: if appt.serialno != self.serialno: return self.connect_listview_signals(False) self.appointment_model.set_selected_appointments(appts) self.connect_listview_signals() def update_selected_appointment(self, appt): self.primary_slots = [] self._chosen_slot = None self.enable_scheduling_buttons() def selection_changed(self, selection): LOGGER.debug("ScheduleControl.selection_changed") self.update_search_criteria_webview() self.enable_scheduling_buttons() try: app = self.appointment_model.data(selection.indexes()[-1], QtCore.Qt.UserRole) except IndexError: app = self.appointment_model.currentAppt self.appointment_selected.emit(app) def appointment_2x_clicked(self, index): LOGGER.debug("ScheduleControl.appointment_clicked") self.show_first_appointment.emit() def clear(self): self.reset_browsing_webview() self.appointment_model.clear() self.reset() def reset(self): self.reset_browsing_webview() self.primary_slots = [] self.secondary_slots = [] self._chosen_slot = None self.cancel_search_mode() self.finding_joint_appointments = False def show_settings_dialog(self): ''' show the settings dialog ''' LOGGER.info("showing the settings dialog") dl = ApptSettingsDialog(self) if dl.exec_(): self.selection_changed( self.appt_listView.selectionModel().selection()) @property def _chosen_slot_no(self): try: return self.primary_slots.index(self._chosen_slot) except ValueError: return 0 def show_next_appt(self): self.set_search_future() try: self._chosen_slot = self.primary_slots[self._chosen_slot_no + 1] self.chosen_slot_changed.emit() except IndexError: self.show_next_day() def show_prev_appt(self): self.set_search_past() try: i = self._chosen_slot_no - 1 if i < 0: raise IndexError self._chosen_slot = self.primary_slots[i] self.chosen_slot_changed.emit() except IndexError: self.show_prev_day() def show_next_day(self): ''' catches the signal to make a big jump forwards. will jump a week if in week view ''' self._chosen_slot = None self.set_search_future() self.diary_widget.step_date(True) def show_prev_day(self): ''' catches the signal to make a big jump backwords. will jump a week if in week view ''' self._chosen_slot = None self.set_search_past() self.diary_widget.step_date(False) @property def all_slots(self): for slot in self.primary_slots: yield slot for slot in self.secondary_slots: yield slot def clear_slots(self): # self.reset_browsing_webview() self._chosen_slot = None self.primary_slots = [] self.secondary_slots = [] def reset_browsing_webview(self): self.browsing_webview.setHtml("") def set_primary_slots(self, slots): self.primary_slots = [] for slot in sorted(slots): if (slot.dent in self.appt1_clinicians and slot.day_no not in self.excluded_days): self.primary_slots.append(slot) def set_secondary_slots(self, slots): LOGGER.debug("filtering secondary slots %s", slots) self.secondary_slots = [] for slot in sorted(slots): slot.is_primary = False if (slot.dent in self.appt2_clinicians and slot.day_no not in self.excluded_days): self.secondary_slots.append(slot) def set_slots_from_day_app_data(self, app_data): if self.app1_is_scheduled: self.set_primary_slots([]) else: self.set_primary_slots( app_data.slots(self.app1_length, self.ignore_emergency_spaces())) app2_slots = [] if self.is_searching_for_double_appointments and \ not self.app2_is_scheduled: self.set_secondary_slots( app_data.slots(self.app2_length, self.ignore_emergency_spaces(), busy_serialno=self.serialno)) app1_slots = set([]) if self.app1_is_scheduled: iterset = [self.appointment_model.currentAppt.to_freeslot()] else: iterset = self.primary_slots for app1_slot in iterset: for app2_slot in self.secondary_slots: wait = app1_slot.wait_time(self.app1_length, self.app2_length, app2_slot) if wait is not None and wait <= MAX_WAIT: app2_slots.append(app2_slot) app1_slots.add(app1_slot) if not self.app1_is_scheduled: self.set_primary_slots(app1_slots) self.set_secondary_slots(app2_slots) def get_weekview_slots(self, weekdates): ''' calculate available slots for weekdates (list of QDates) ''' today = QtCore.QDate.currentDate() startday = today if today in weekdates else weekdates[0] # monday sunday = weekdates[6] # sunday # check for suitable apts in the selected WEEK! all_slots = appointments.future_slots( startday.toPyDate(), sunday.toPyDate(), self.appt1_clinicians, busy_serialno=self.serialno, override_emergencies=self.ignore_emergency_spaces()) if self.app1_is_scheduled: self.set_primary_slots([]) else: self.set_primary_slots( appointments.getLengthySlots(all_slots, self.app1_length)) app2_slots = [] if self.is_searching_for_double_appointments and \ not self.app2_is_scheduled: all_slots = appointments.future_slots( startday.toPyDate(), sunday.toPyDate(), self.appt2_clinicians, busy_serialno=self.serialno, override_emergencies=self.ignore_emergency_spaces()) self.set_secondary_slots( appointments.getLengthySlots(all_slots, self.app2_length)) app1_slots = set([]) if self.app1_is_scheduled: iterset = [self.appointment_model.currentAppt.to_freeslot()] else: iterset = self.primary_slots for app1_slot in iterset: for app2_slot in self.secondary_slots: wait = app1_slot.wait_time(self.app1_length, self.app2_length, app2_slot) if wait is not None and wait <= MAX_WAIT: app2_slots.append(app2_slot) app1_slots.add(app1_slot) if not self.app1_is_scheduled: self.set_primary_slots(app1_slots) self.set_secondary_slots(app2_slots) @property def chosen_2nd_slots(self): if not (self.appointment_model.currentAppt is None or self.appointment_model.secondaryAppt is None): for app2_slot in self.secondary_slots: wait = self.chosen_slot.wait_time(self.app1_length, self.app2_length, app2_slot) if wait is not None and wait <= MAX_WAIT: yield app2_slot else: LOGGER.debug("no appointments selected") @property def last_appt_date(self): ''' returns the latest date of patient's appointments, or today's date if none found ''' last_d = QtCore.QDate.currentDate().toPyDate() for appt in self.appointment_model.scheduledList: if appt.date > last_d: last_d = appt.date return last_d @property def is_searching(self): ''' are we looking for a slot? ''' app1 = self.appointment_model.currentAppt if app1 is not None and app1.unscheduled: return True app2 = self.appointment_model.secondaryAppt if app2 is not None and app2.unscheduled: return True return False @property def is_searching_for_double_appointments(self): return self.appointment_model.secondaryAppt is not None @property def search_again(self): ''' this determines whether it is worth continuing (on a different date) ''' LOGGER.debug("search again is_searching = %s", self.is_searching) LOGGER.debug("search again forwards = %s", self.search_mode == self.SEARCHING_FORWARDS) LOGGER.debug("search again backwards = %s", self.search_mode == self.SEARCHING_BACKWARDS) LOGGER.debug("search again n_clinicians = %s", len(self.selectedClinicians)) LOGGER.debug("search again still have slots = %s", len(self.primary_slots) == 0) search_again = (self.is_searching and self.search_mode != self.NOT_SEARCHING and len(self.selectedClinicians) > 0 and len(self.primary_slots) == 0) LOGGER.debug("therefore searce again = %s", search_again) return search_again def find_first_appointment(self): LOGGER.debug("find_first_appointment") self.show_first_appointment.emit() @property def chosen_slot(self): if self.primary_slots == []: if self.app1_is_scheduled: return self.appointment_model.currentAppt.to_freeslot() return None if self._chosen_slot is None: if self.searching_past: self._chosen_slot = self.primary_slots[-1] else: self._chosen_slot = self.primary_slots[0] return self._chosen_slot def set_chosen_slot(self, slot): self._chosen_slot = slot def set_chosen_2nd_slot(self, slot): ''' user has clicked on a secondary slot - we need to switch the appointments over! ''' LOGGER.debug("set_chosen_2nd_slot %s", slot) model = self.appointment_model.selection_model selection = model.selection() selection.swap(0, 1) model.select(selection, model.ClearAndSelect) self.selection_changed( self.appointment_model.selection_model.selection()) self.appointment_model.selection_model.emitSelectionChanged( selection, QtCore.QItemSelection()) slot.is_primary = True self.set_chosen_slot(slot) self.chosen_slot_changed.emit() @property def excluded_days(self): return ApptSettingsDialog.excluded_days def show_pt_diary(self): if self.pt is None: QtWidgets.QMessageBox.information(self, _("error"), _("No patient selected")) return def _find_appt(appt): dl.accept() self.find_appt.emit(appt) def _start_scheduling(custom): ''' if custom, the dialog will be raised giving day of week options etc ''' dl.accept() self.get_data() self.appointment_model.selection_model.reset() appts = pt_diary_widget.selected_appointments self.update_appt_selection(appts) QtCore.QTimer.singleShot( 100, partial(self.schedule_signal.emit, custom)) pt_diary_widget = PtDiaryWidget() pt_diary_widget.find_appt.connect(_find_appt) pt_diary_widget.start_scheduling_signal.connect(_start_scheduling) pt_diary_widget.set_patient(self.pt) dl = BaseDialog(self, remove_stretch=True) dl.insertWidget(pt_diary_widget) dl.cancel_but.setText(_("OK")) dl.apply_but.hide() dl.setMinimumWidth(800) dl.setMaximumWidth(800) if not dl.exec_(): # dialog is only accepted by subroutines above self.appointment_model.load_from_database(self.pt) self.enable_scheduling_buttons() def enable_scheduling_buttons(self): enabled = self.is_searching for but in (self.next_appt_button, self.next_day_button, self.prev_appt_button, self.prev_day_button, self.first_appt_button): but.setEnabled(enabled) self.update_search_criteria_webview() def begin_make_appointment(self, custom): ''' this is when an external widget calls for us to start the process of starting an appointment if custom is True, then the ApptSettingsDialog is shown. ''' LOGGER.debug("begin_make_appointment custom = %s", custom) self.set_mode(self.SCHEDULE_MODE) self.enable_scheduling_buttons() LOGGER.debug("checking appointment settings %s", ApptSettingsDialog.is_default_settings()) if custom == True: self.show_settings_dialog() elif not ApptSettingsDialog.is_default_settings(): dl = ApptSettingsResetDialog(self) if dl.exec_(): if dl.show_settings_dialog: self.show_settings_dialog() else: ApptSettingsDialog.reset() def check_schedule_status(self, automatic): ''' this is called by the diary widget when the books have been laid out whilst in schedule mode. Inform the user ''' assert self.mode == self.SCHEDULE_MODE, "not in schedule mode" LOGGER.debug("=" * 40) LOGGER.debug("check_schedule_status %s", self.diary_widget.selected_date()) if automatic: # this has been called by a timer update to the diary, # and therefore NOT user interaction LOGGER.debug("automatic call to check_schedule_status... ignoring") return try: date_ = self.diary_widget.selected_date().toPyDate() except AttributeError: # self.diary_widget is None? LOGGER.debug("date check error", exc_info=1) date_ = localsettings.currentDay() if not self.appointment_model.selectedAppts: LOGGER.debug("schedule status - no selected appointments") self.advice_signal.emit( _("Please select an appointment to begin scheduling"), 0) elif self.app1_is_scheduled: LOGGER.debug("schedule status - appt 1 is scheduled") if self.appointment_model.secondaryAppt is None: self.advice_signal.emit(_("appointment is already scheduled"), 0) elif self.app2_is_scheduled: LOGGER.debug("schedule status - appt 2 also scheduled") self.advice_signal.emit(_("Joint appointment Scheduled"), 0) elif not list(self.chosen_2nd_slots): LOGGER.debug("schedule status - no slots for appt 2") self.advice_signal.emit( _("Joint appointment is not possible with the " "chosen primary appointment"), 1) elif self.search_again: LOGGER.debug("schedule status - search again") if date_ > localsettings.BOOKEND: LOGGER.debug( "schedule status - search again has reached bookend") self.advice_signal.emit( '''<b>%s<br />%s</b><hr /><em>(%s)</em> <ul> <li>%s</li><li>%s</li><li>%s</li><li>%s</li> </ul>''' % (_("This date is beyond the diary limit."), _("Please search again with different criteria."), _("for instance..."), _("no excluded days"), _("ignore emergencies"), _("add or view more clinicians"), _("or you have requested an impossible appointment!")), 1) self.cancel_search_mode() self.diary_widget.set_date(localsettings.currentDay()) elif date_ < localsettings.currentDay(): LOGGER.debug( "schedule status - search again has reached the past") self.advice_signal.emit( _("You can't schedule an appointment in the past"), 1) self.diary_widget.set_date(localsettings.currentDay()) else: LOGGER.debug("schedule status - calling step date") self.diary_widget.step_date(self.searching_future) elif date_ > localsettings.BOOKEND: LOGGER.debug("schedule status - beyond bookend") self.advice_signal.emit(_("This date is beyond the diary limit."), 1) elif date_ < localsettings.currentDay(): LOGGER.debug("schedule status - in the past!") self.advice_signal.emit( _("You can't schedule an appointment in the past"), 1) self.diary_widget.set_date(localsettings.currentDay()) elif self.chosen_slot is None and not list(self.chosen_2nd_slots): LOGGER.debug("schedule status - no 2nd slots") if self.diary_widget: if self.diary_widget.viewing_week: message = "%s %s" % ( _("in this week"), self.diary_widget.selected_date().weekNumber()) else: message = "%s (%s)" % ( _("on this day"), localsettings.formatDate( self.diary_widget.selected_date().toPyDate())) else: message = "" # should only happen if __name__ == "__main__" message = "%s %s" % (_("No Slots Found"), message) self.advice_signal.emit(message, 0) else: LOGGER.debug("schedule status - nothing to do") LOGGER.debug("=" * 40) @property def _dentist_message(self): if not self.appointment_model.dentists_involved: return "" if ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_SELECTED: return _("Specified Dentist only") elif ApptSettingsDialog.dentist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_DENT: return _("Any Dentist") else: # CLINICIAN_ANY return _("Any Clinician") @property def _hygienist_message(self): if not self.appointment_model.hygienists_involved: return "" if ApptSettingsDialog.hygienist_policy == \ ApptSettingsDialog.CLINICIAN_SELECTED: return _("Specified Hygienist only for hyg appts") if ApptSettingsDialog.hygienist_policy == \ ApptSettingsDialog.CLINICIAN_ANY_HYG: return _("Any Hygienist for hyg appts") else: # CLINICIAN_ANY return _("Any Clinician for hyg appts") @property def _joint_message(self): if self.is_searching_for_double_appointments: return _("Joint Appointments") return "" @property def _emergency_message(self): if self.ignore_emergency_spaces(): return "%s" % _("Overwrite Emergencies") return "" @property def _day_message(self): if self.excluded_days == []: return _("Any Day") days = [] for i, day in enumerate((_("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")), 1): if i not in self.excluded_days: days.append(day) return ", ".join(days) def update_search_criteria_webview(self): if self.appointment_model.currentAppt is None: html = _("No Appointment Selected") else: html = SETTINGS_HTML % ( _("Search Settings"), '</li><li class="search-settings-item">'.join([ s for s in (self._dentist_message, self._hygienist_message, self._joint_message, self._emergency_message, self._day_message) if s != "" ])) self.search_criteria_webview.setHtml(html) def update_highlighted_appointment(self): ''' the diary widget selected appointment has changed. ''' app = self.diary_widget.highlighted_appointment LOGGER.debug("appointment highlighted %s", app) if app is None: self.reset_browsing_webview() return if self.mode == self.NOTES_MODE: self.notes_label.setText( "<h3>%s</h3>%s<br />%s" % (_("View/edit today's notes for "), app.name, app.serialno)) return self.notes_label.setText("") if self.mode != self.BROWSE_MODE: return feedback = FEEDBACK % ( app.name, app.serialno, localsettings.readableDate( self.diary_widget.selected_date().toPyDate()), "%02d:%02d" % (app.start // 100, app.start % 100), "%02d:%02d" % (app.end // 100, app.end % 100), '</li><li class="trt">'.join( [val for val in (app.trt1, app.trt2, app.trt3) if val != ""])) if app.memo != "": feedback += "<hr />%s<br /><i>%s</i>" % (_("Memo"), app.memo) try: datestamp = app.timestamp.date() feedback += \ "<hr />%s<br />%s (%s %s)" % ( _("Made"), localsettings.formatDate(datestamp), _("at"), localsettings.pyTimeToHumantime( app.timestamp)) except AttributeError: pass if app.mh_form_check_date or app.mh_form_required: feedback += "<hr />" if app.mh_form_check_date: feedback += "%s %s<br />" % (_("last mh form"), localsettings.formatDate( app.mh_form_check_date)) if app.mh_form_required: feedback += "%s" % _("MH CHECK REQUIRED") feedback = "%s<body></html>" % feedback self.browsing_webview.setHtml(feedback)
def __init__(self, parent=None): QtWidgets.QStackedWidget.__init__(self, parent) self.diary_widget = parent self.patient_label = QtWidgets.QLabel() icon = QtGui.QIcon(":/search.png") self.get_patient_button = QtWidgets.QPushButton(icon, "") self.get_patient_button.setMaximumWidth(40) self.appt_listView = DraggableList(self) self.block_listView = DraggableList(self) self.item_delegate = ColouredItemDelegate(self) self.appointment_model = SimpleListModel(self) self.appt_listView.setModel(self.appointment_model) self.appt_listView.setItemDelegate(self.item_delegate) self.appt_listView.setSelectionModel( self.appointment_model.selection_model) self.appt_listView.setSelectionMode( QtWidgets.QListView.ContiguousSelection) block_model = BlockListModel(self) self.block_listView.setModel(block_model) icon = QtGui.QIcon(":vcalendar.png") diary_button = QtWidgets.QPushButton(icon, _("Diary")) diary_button.setToolTip(_("Open the patient's diary")) icon = QtGui.QIcon(":settings.png") settings_button = QtWidgets.QPushButton(icon, _("Options")) settings_button.setToolTip(_("Appointment Settings")) icon = QtGui.QIcon(":back.png") self.prev_appt_button = QtWidgets.QPushButton(icon, "") self.prev_appt_button.setToolTip(_("Previous appointment")) icon = QtGui.QIcon(":forward.png") self.next_appt_button = QtWidgets.QPushButton(icon, "") self.next_appt_button.setToolTip(_("Next available appointment")) icon = QtGui.QIcon(":forward.png") self.next_day_button = QtWidgets.QPushButton(icon, "") self.next_day_button.setToolTip(_("Next Day or Week")) icon = QtGui.QIcon(":back.png") self.prev_day_button = QtWidgets.QPushButton(icon, "") self.prev_day_button.setToolTip(_("Previous Day or Week")) icon = QtGui.QIcon(":first.png") self.first_appt_button = QtWidgets.QPushButton(icon, "") self.first_appt_button.setToolTip(_("First available appointment")) self.appt_controls_frame = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(self.appt_controls_frame) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) layout.addWidget(diary_button, 0, 0, 1, 2) layout.addWidget(settings_button, 0, 3, 1, 2) layout.addWidget(self.prev_day_button, 1, 0) layout.addWidget(self.prev_appt_button, 1, 1) layout.addWidget(self.first_appt_button, 1, 2) layout.addWidget(self.next_appt_button, 1, 3) layout.addWidget(self.next_day_button, 1, 4) self.appt_controls_frame.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)) self.search_criteria_webview = OMWebView(self) self.search_criteria_webview.setMinimumHeight(100) self.search_criteria_webview.setHtml( _("No appointment selected for scheduling")) # now arrange the stacked widget # page 0 - Browsing mode self.browsing_webview = OMWebView(self) self.reset_browsing_webview() self.addWidget(self.browsing_webview) # page 1 -- scheduling mode widg = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(widg) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.patient_label, 0, 0) layout.addWidget(self.get_patient_button, 0, 1) layout.addWidget(self.appt_listView, 2, 0, 1, 2) layout.addWidget(self.appt_controls_frame, 3, 0, 1, 2) layout.addWidget(self.search_criteria_webview, 4, 0, 1, 2) self.addWidget(widg) # page 2 -- blocking mode widg = QtWidgets.QWidget() layout = QtWidgets.QVBoxLayout(widg) layout.addWidget(self.block_listView) self.addWidget(widg) # page 4 -- notes mode self.notes_label = QtWidgets.QLabel( _("Select a patient to edit notes")) self.addWidget(self.notes_label) # connect signals self.get_patient_button.clicked.connect(self.find_patient) settings_button.clicked.connect(self.show_settings_dialog) self.prev_appt_button.clicked.connect(self.show_prev_appt) self.next_appt_button.clicked.connect(self.show_next_appt) self.prev_day_button.clicked.connect(self.show_prev_day) self.next_day_button.clicked.connect(self.show_next_day) self.first_appt_button.clicked.connect(self.find_first_appointment) diary_button.clicked.connect(self.show_pt_diary) self.connect_listview_signals() self.appt_listView.doubleClicked.connect(self.appointment_2x_clicked)
class AdvancedNamesDialog(BaseDialog): pt = None pseudonyms = [] def __init__(self, parent=None): BaseDialog.__init__(self, parent) self.setWindowTitle(_("Advanced Names Dialog")) label = WarningLabel( _("Previous Surnames, Nicknames, and alternate " "spelling can help when searching for patients")) self.browser = OMWebView(self) self.browser.linkClicked.connect(self.link_clicked) self.insertWidget(label) self.insertWidget(self.browser) self.cancel_but.hide() self.enableApply() def link_clicked(self, url): url_text = url.toString() if url_text == "om://add_psn": self.add_previous_surname() elif url_text == "om://add_alt": self.add_alt_name() else: m = re.match(r"om://edit_name_(\d+)", url_text) if m: ix = int(m.groups()[0]) self.edit_name(ix) def add_previous_surname(self, psn=""): LOGGER.info("add a surname") if psn == "": message = _("Please enter a previous surname") else: message = "%s '%s' %s" % (_("Save"), psn, _("as a previous surname?")) surname, result = \ QtWidgets.QInputDialog.getText(self, _("Input required"), message, QtWidgets.QLineEdit.Normal, psn) if result: LOGGER.info("adding %s as a previous surname", surname) db = connect() cursor = db.cursor() cursor.execute(INSERT_PSN_QUERY, (self.pt.serialno, surname.upper())) cursor.close() self.set_patient(self.pt) return True def add_alt_name(self): LOGGER.info("add an alternative name") dl = AltNameEntryDialog(self) if dl.exec_(): fname = dl.fname_le.text() sname = dl.sname_le.text() comment = dl.comment_le.text() db = connect() cursor = db.cursor() cursor.execute(INSERT_ALT_QUERY, (self.pt.serialno, None if not fname else fname, None if not sname else sname, comment)) cursor.close() self.set_patient(self.pt) def edit_name(self, ix): LOGGER.info("edit name %s", ix) for pseudonym in self.pseudonyms: if pseudonym.ix == ix: break dl = AltNameEntryDialog(self) dl.label.setText( _("Edit this name (or leave both name fields blank " "to delete a reference to this name)")) dl.fname_le.setText(pseudonym.fname) dl.sname_le.setText(pseudonym.sname) dl.comment_le.setText(pseudonym.comment) dl.enableApply(False) if dl.exec_(): fname = dl.fname_le.text() sname = dl.sname_le.text() comment = dl.comment_le.text() db = connect() cursor = db.cursor() if sname == "" and fname == "": cursor.execute(DELETE_QUERY, (ix, )) else: cursor.execute(UPDATE_ALT_QUERY, (None if not fname else fname, None if not sname else sname, comment, ix)) cursor.close() self.set_patient(self.pt) def set_patient(self, pt): ''' pass a patient object to set the serialnumber and name fields. ''' self.pt = pt db = connect() cursor = db.cursor() cursor.execute(PSEUDONYMS_QUERY, (pt.serialno, )) alts = [] for row in cursor.fetchall(): pseudonym = Pseudonym(*row) alts.append(pseudonym) self.pseudonyms = alts previous = '</li><li>'.join( [p.html() for p in alts if p.comment == "previous surname"] + ['<a href="om://add_psn">%s</a>' % _("Add New")]) alts = '</li><li>'.join( [p.html() for p in alts if p.comment != "previous surname"] + ['<a href="om://add_alt">%s</a>' % _("Add New")]) self.browser.setHtml(HTML % (self.fname, self.sname, previous, alts)) self.browser.delegate_links() @property def sname(self): try: return self.pt.sname except AttributeError: pass @property def fname(self): try: return self.pt.fname except AttributeError: pass def check_save_previous_surname(self, surname): self.show() if self.add_previous_surname(surname): self.exec_() def sizeHint(self): return QtCore.QSize(400, 500)