Example #1
0
class MaintenanceApp(SearchableAppWindow):
    """Maintenance app"""

    app_name = _(u'Maintenance')
    gladefile = 'maintenance'
    search_table = WorkOrderView
    search_label = _(u'matching:')
    report_table = WorkOrdersReport
    embedded = True

    _status_mapper = {
        'pending': Or(WorkOrder.status == WorkOrder.STATUS_OPENED,
                      WorkOrder.status == WorkOrder.STATUS_APPROVED),
        'in-progress': WorkOrder.status == WorkOrder.STATUS_WORK_IN_PROGRESS,
        'finished': WorkOrder.status == WorkOrder.STATUS_WORK_FINISHED,
        'closed': Or(WorkOrder.status == WorkOrder.STATUS_CANCELLED,
                     WorkOrder.status == WorkOrder.STATUS_CLOSED),
        }

    #
    # Application
    #

    def create_actions(self):
        group = get_accels('app.maintenance')
        actions = [
            # File
            ("OrderMenu", None, _(u"Order")),
            ("NewOrder", None, _(u"Work order..."),
             group.get("new_order")),

            # Search
            ("Products", None, _(u"Products..."),
             group.get("search_products")),
            ("Services", None, _(u"Services..."),
             group.get("search_services")),
            ("Categories", None, _(u"Categories..."),
             group.get("search_categories")),

            # Order
            ("Edit", gtk.STOCK_EDIT, _(u"Edit..."),
             group.get('order_edit'),
             _(u"Edit the selected order")),
            ("Finish", gtk.STOCK_APPLY, _(u"Finish..."),
             group.get('order_finish'),
             _(u"Finish the selected order")),
            ("Cancel", gtk.STOCK_CANCEL, _(u"Cancel..."),
             group.get('order_cancel'),
             _(u"Cancel the selected order")),
            ("Details", gtk.STOCK_INFO, _(u"Details..."),
             group.get('order_details'),
             _(u"Show details of the selected order")),
            ("PrintQuote", None, _(u"Print quote..."),
             group.get('order_print_quote'),
             _(u"Print a quote report of the selected order")),
            ("PrintReceipt", None, _(u"Print receipt..."),
             group.get('order_print_receipt'),
             _(u"Print a receipt of the selected order")),
            ]

        self.maintenance_ui = self.add_ui_actions("", actions,
                                                  filename="maintenance.xml")

        self.Edit.set_short_label(_(u"Edit"))
        self.Finish.set_short_label(_(u"Finish"))
        self.Edit.props.is_important = True
        self.Finish.props.is_important = True

        self.set_help_section(_(u"Maintenance help"), 'app-maintenance')
        self.popup = self.uimanager.get_widget('/MaintenanceSelection')

    def create_ui(self):
        if api.sysparam(self.store).SMART_LIST_LOADING:
            self.search.search.enable_lazy_search()

        self.app.launcher.add_new_items([
            self.NewOrder,
            ])
        self.app.launcher.add_search_items([
            self.Products,
            self.Services,
            self.Categories,
            ])

        self.search.set_summary_label(
            column='total',
            label=('<b>%s</b>' %
                   api.escape(_('Total:'))),
            format='<b>%s</b>',
            parent=self.get_statusbar_message_area())

        self.results.set_cell_data_func(self._on_results__cell_data_func)

    def activate(self, params):
        self.app.launcher.NewToolItem.set_tooltip(
            _(u"Create a new work order"))
        self.app.launcher.SearchToolItem.set_tooltip(
            _(u"Search for work order categories"))

        self._update_view()

    def deactivate(self):
        self.uimanager.remove_ui(self.maintenance_ui)

    def setup_focus(self):
        self.search.refresh()

    def new_activate(self):
        self._new_order()

    def search_activate(self):
        self.run_dialog(ProductSearch, self.store,
                        hide_footer=True, hide_toolbar=True)

    def search_completed(self, results, states):
        if len(results):
            return

        base_msg = ''
        url_msg = ''
        state = states[1]
        if state and state.value is None:
            # Base search with no filters
            base_msg = _(u"No work orders could be found.")
            url = u"<a href='new_order'>%s</a>" % (
                api.escape(_(u"create a new work order")), )
            url_msg = _(u"Would you like to %s ?") % (url, )
        else:
            kind, value = state.value.value.split(':')
            # Search filtering by status
            if kind == 'status':
                if value == 'pending':
                    base_msg = _(u"No pending work orders could be found.")
                elif value == 'in-progress':
                    base_msg = _(u"No work orders in progress could be found.")
                elif value == 'finished':
                    base_msg = _(u"No finished work orders could be found.")
                elif value == 'closed':
                    base_msg = _(u"No closed or cancelled work "
                                 u"orders could be found.")
            # Search filtering by category
            elif kind == 'category':
                base_msg = _(u"No work orders in the category %s "
                             u"could be found.") % (
                                 '<b>%s</b>' % (value, ), )
                url = u"<a href='new_order?%s'>%s</a>" % (
                    urllib.quote(value.encode('utf-8')),
                    api.escape(_(u"create a new work order")), )
                url_msg = _(u"Would you like to %s ?") % (url, )

        if not base_msg:
            return

        msg = '\n\n'.join([base_msg, url_msg])
        self.search.set_message(msg)

    #
    # SearchableAppWindow
    #

    def create_filters(self):
        self.set_text_field_columns(['equipment', 'client_name'])

        self.main_filter = ComboSearchFilter(_('Show'), [])
        combo = self.main_filter.combo
        combo.color_attribute = 'color'
        combo.set_row_separator_func(self._on_main_filter__row_separator_func)

        self.executer.add_filter_query_callback(
            self.main_filter,
            self._on_main_filter__query_callback)
        self.add_filter(self.main_filter, SearchFilterPosition.TOP)

        self._update_filters()

    def get_columns(self):
        return [
            SearchColumn('identifier', title=_(u'#'), data_type=int,
                         width=60, sorted=True, format='%04d'),
            SearchColumn('work_order.status_str', title=_(u'Status'),
                         search_attribute='status', data_type=str,
                         valid_values=self._get_status_values(), visible=False),
            SearchColumn('category_name', title=_(u'Category'),
                         data_type=str, visible=False),
            SearchColumn('equipment', title=_(u'Equipment'),
                         data_type=str, expand=True, pack_end=True),
            Column('category_color', title=_(u'Equipment'), column='equipment',
                   data_type=gtk.gdk.Pixbuf, format_func=render_pixbuf),
            SearchColumn('client_name', title=_(u'Client'),
                         data_type=str),
            SearchColumn('open_date', title=_(u'Open date'),
                         data_type=datetime.date),
            SearchColumn('approve_date', title=_(u'Approval date'),
                         data_type=datetime.date, visible=False),
            SearchColumn('finish_date', title=_(u'Finish date'),
                         data_type=datetime.date, visible=False),
            SearchColumn('total', title=_(u'Total'),
                         data_type=currency),
            ]

    #
    # Private
    #

    def _get_main_query(self, state):
        item = state.value
        if item is None:
            return

        kind, value = item.value.split(':')
        if kind == 'category':
            return WorkOrder.category_id == item.id
        if kind == 'status':
            return self._status_mapper[value]
        else:
            raise AssertionError(kind, value)

    def _get_status_values(self):
        return ([(_('Any'), None)] +
                [(v, k) for k, v in WorkOrder.statuses.items()])

    def _update_view(self):
        self.search.refresh()
        self._update_list_aware_view()

    def _update_list_aware_view(self):
        selection = self.results.get_selected()
        has_selected = bool(selection)
        has_quote = has_selected and bool(selection.work_order.defect_detected)

        can_edit = (has_selected and (selection.work_order.can_approve() or
                                      selection.work_order.can_start() or
                                      selection.work_order.can_finish()))
        self.set_sensitive([self.Edit], can_edit)
        self.set_sensitive([self.Details], has_selected)
        self.set_sensitive([self.Finish],
                           has_selected and selection.work_order.can_finish())
        self.set_sensitive([self.Cancel],
                           has_selected and selection.work_order.can_cancel())
        self.set_sensitive([self.PrintReceipt],
                           has_selected and selection.work_order.is_finished())
        self.set_sensitive([self.PrintQuote], has_quote)

    def _update_filters(self):
        options = [
            _FilterItem(_(u'Pending'), 'status:pending'),
            _FilterItem(_(u'In progress'), 'status:in-progress'),
            _FilterItem(_(u'Finished'), 'status:finished'),
            _FilterItem(_(u'Closed or cancelled'), 'status:closed'),
            ]

        categories = list(self.store.find(WorkOrderCategory))
        if len(categories):
            options.append(_FilterItem('sep', 'sep'))
        for category in categories:
            value = 'category:%s' % (category.name, )
            options.append(_FilterItem(category.name, value,
                                       color=category.color,
                                       obj_id=category.id))

        self.main_filter.update_values(
            [(_(u'All work orders'), None)] +
            [(item.name, item) for item in options])

    def _new_order(self, category=None):
        with api.trans() as store:
            self.run_dialog(WorkOrderEditor, store,
                            category=store.fetch(category))

        if store.committed:
            self._update_view()
            # A category may have been created on the editor
            self._update_filters()

    def _edit_order(self):
        selection = self.results.get_selected()
        with api.trans() as store:
            self.run_dialog(WorkOrderEditor, store,
                            model=store.fetch(selection.work_order))

        if store.committed:
            self._update_view()
            # A category may have been created on the editor
            self._update_filters()

    def _finish_order(self):
        if yesno(_(u"This will finish the selected order, marking the "
                   u"work as done. Are you sure?"),
                 gtk.RESPONSE_NO, _(u"Don't finish"), _(u"Finish order")):
            return

        selection = self.results.get_selected()
        with api.trans() as store:
            work_order = store.fetch(selection.work_order)
            work_order.finish()

        self._update_view()

    def _cancel_order(self):
        if yesno(_(u"This will cancel the selected order. Are you sure?"),
                 gtk.RESPONSE_NO, _(u"Don't cancel"), _(u"Cancel order")):
            return

        selection = self.results.get_selected()
        with api.trans() as store:
            work_order = store.fetch(selection.work_order)
            work_order.cancel()
        self._update_view()

    def _run_order_details_dialog(self):
        selection = self.results.get_selected()
        self.run_dialog(WorkOrderEditor, self.store,
                        model=selection.work_order, visual_mode=True)

    def _run_order_category_dialog(self):
        with api.trans() as store:
            self.run_dialog(WorkOrderCategoryDialog, store)
        self._update_view()
        self._update_filters()

    #
    # Kiwi Callbacks
    #

    def _on_main_filter__row_separator_func(self, model, titer):
        if model[titer][0] == 'sep':
            return True
        return False

    def _on_main_filter__query_callback(self, state):
        return self._get_main_query(state)

    def _on_results__cell_data_func(self, column, renderer, wov, text):
        if not isinstance(renderer, gtk.CellRendererText):
            return text

        work_order = wov.work_order
        is_finished = work_order.status == WorkOrder.STATUS_WORK_FINISHED
        is_closed = work_order.status in [WorkOrder.STATUS_CANCELLED,
                                          WorkOrder.STATUS_CLOSED]
        is_late = work_order.is_late()

        for prop, is_set, value in [
                ('strikethrough', is_closed, True),
                ('style', is_finished, pango.STYLE_ITALIC),
                ('weight', is_late, pango.WEIGHT_BOLD)
                ]:
            renderer.set_property(prop + '-set', is_set)
            if is_set:
                renderer.set_property(prop, value)

        return text

    def on_results__right_click(self, results, result, event):
        self.popup.popup(None, None, None, event.button, event.time)

    def on_results__row_activated(self, klist, purchase_order_view):
        if self.Edit.get_sensitive():
            self._edit_order()
        elif self.Details.get_sensitive():
            self._run_order_details_dialog()
        else:
            assert False

    def _on_results__double_click(self, results, order):
        if self.Edit.get_sensitive():
            self._edit_order()
        elif self.Details.get_sensitive():
            self._run_order_details_dialog()
        else:
            assert False

    def on_results__selection_changed(self, results, selected):
        self._update_list_aware_view()

    def on_results__activate_link(self, results, uri):
        if not uri.startswith('new_order'):
            return

        if '?' in uri:
            category_name = unicode(urllib.unquote(uri.split('?', 1)[1]))
            category = self.store.find(WorkOrderCategory,
                                       name=category_name).one()
        else:
            category = None

        self._new_order(category=category)

    def on_NewOrder__activate(self, action):
        self._new_order()

    def on_Edit__activate(self, action):
        self._edit_order()

    def on_Finish__activate(self, action):
        self._finish_order()

    def on_Cancel__activate(self, action):
        self._cancel_order()

    def on_Details__activate(self, action):
        self._run_order_details_dialog()

    def on_PrintQuote__activate(self, action):
        workorderview = self.results.get_selected()
        print_report(WorkOrderQuoteReport, workorderview.work_order)

    def on_PrintReceipt__activate(self, action):
        workorderview = self.results.get_selected()
        print_report(WorkOrderReceiptReport, workorderview.work_order)

    def on_Products__activate(self, action):
        self.run_dialog(ProductSearch, self.store,
                        hide_footer=True, hide_toolbar=True)

    def on_Services__activate(self, action):
        self.run_dialog(ServiceSearch, self.store)

    def on_Categories__activate(self, action):
        self._run_order_category_dialog()
Example #2
0
class BaseAccountWindow(SearchableAppWindow):
    embedded = True

    #
    # Application
    #

    def create_ui(self):
        if api.sysparam(self.store).SMART_LIST_LOADING:
            self.search.search.enable_lazy_search()
        self.results.set_selection_mode(gtk.SELECTION_MULTIPLE)
        self.search.set_summary_label(column='value',
                                      label='<b>%s</b>' % (_('Total'), ),
                                      format='<b>%s</b>',
                                      parent=self.get_statusbar_message_area())
        self.results.set_cell_data_func(self._on_results__cell_data_func)

    def search_completed(self, results, states):
        if len(results):
            return

        state = states[1]
        if state and state.value is None:
            not_found = _("No payments found.")
            payment_url = '<a href="new_payment">%s</a>?' % (
                api.escape(_("create a new payment")))
            new_payment = _("Would you like to %s") % (payment_url, )
            msg = "%s\n\n%s" % (not_found, new_payment)
        else:
            v = state.value.value
            if v == 'status:late':
                msg = _("No late payments found.")
            elif v == 'status:paid':
                msg = _("No paid payments found.")
            elif v == 'status:not-paid':
                if self.search_table == InPaymentView:
                    msg = _("No payments to receive found.")
                else:
                    msg = _("No payments to pay found.")
            elif v.startswith('category:'):
                category = v.split(':')[1].encode('utf-8')

                not_found = _("No payments in the <b>%s</b> category were found.") % (
                    api.escape(category), )
                payment_url = '<a href="new_payment?%s">%s</a>?' % (
                    urllib.quote(category),
                    _("create a new payment"))
                msg = "%s\n\n%s" % (
                    not_found,
                    _("Would you like to %s") % (payment_url, ))
            else:
                return

        self.search.set_message(msg)

    #
    # Public API
    #

    def add_payment(self, category=None):
        with api.trans() as store:
            self.run_dialog(self.editor_class, store, category=category)

        if store.committed:
            self._update_filter_items()
            self.search.refresh()
            self.select_result(self.store.find(self.search_table,
                                               id=store.retval.id).one())

    def show_details(self, payment_view):
        """Shows some details about the payment, allowing to edit a few
        properties
        """
        with api.trans() as store:
            payment = store.fetch(payment_view.payment)
            run_dialog(self.editor_class, self, store, payment)

        if store.committed:
            payment_view.sync()
            self.results.update(payment_view)

        return payment

    def show_comments(self, payment_view):
        """Shows a dialog with comments saved on the payment
        @param payment_view: an OutPaymentView or InPaymentView instance
        """
        with api.trans() as store:
            run_dialog(PaymentCommentsDialog, self, store,
                       payment_view.payment)

        if store.committed:
            payment_view.sync()
            self.results.update(payment_view)

    def change_due_date(self, payment_view, order):
        """ Receives a payment_view and change the payment due date
        related to the view.
        @param payment_view: an OutPaymentView or InPaymentView instance
        @param order: a Sale or Purchase instance related to this payment. This
          will be used to show the identifier of the order
        """
        assert payment_view.can_change_due_date()

        with api.trans() as store:
            payment = store.fetch(payment_view.payment)
            order = store.fetch(order)
            run_dialog(PaymentDueDateChangeDialog, self, store,
                       payment, order)

        if store.committed:
            # We need to refresh the whole list as the payment(s) can possibly
            # disappear for the selected view
            self.refresh()

    def change_status(self, payment_view, order, status):
        """Show a dialog do enter a reason for status change
        @param payment_view: an OutPaymentView or InPaymentView instance
        @param order: a Sale or Purchase instance related to this payment. This
          will be used to show the identifier of the order
        @param status: The new status to set the payment to
        """
        with api.trans() as store:
            payment = store.fetch(payment_view.payment)
            order = store.fetch(payment_view.sale)

            if order is None:
                order = store.fetch(payment_view.purchase)

            run_dialog(PaymentStatusChangeDialog, self, store,
                       payment, status, order)

        if store.committed:
            # We need to refresh the whole list as the payment(s) can possibly
            # disappear for the selected view
            self.refresh()

    def create_main_filter(self):
        self.main_filter = ComboSearchFilter(_('Show'), [])

        combo = self.main_filter.combo
        combo.color_attribute = 'color'
        combo.set_row_separator_func(self._on_main_filter__row_separator_func)
        self._update_filter_items()
        self.executer.add_filter_query_callback(
            self.main_filter,
            self._on_main_filter__query_callback)
        self.add_filter(self.main_filter, SearchFilterPosition.TOP)

    def add_filter_items(self, category_type, options):
        categories = PaymentCategory.get_by_type(self.store, category_type)
        items = [(_('All payments'), None)]

        if categories.count() > 0:
            options.append(FilterItem('sep', 'sep'))

        items.extend([(item.name, item) for item in options])
        for c in categories:
            item = FilterItem(c.name, 'category:%s' % (c.name, ),
                              color=c.color,
                              item_id=c.id)
            items.append((item.name, item))

        self.main_filter.update_values(items)

    #
    # Private
    #

    def _create_main_query(self, state):
        item = state.value
        if item is None:
            return None
        kind, value = item.value.split(':')
        payment_view = self.search_table
        if kind == 'status':
            if value == 'paid':
                return payment_view.status == Payment.STATUS_PAID
            elif value == 'not-paid':
                return payment_view.status == Payment.STATUS_PENDING
            elif value == 'late':
                tolerance = api.sysparam(self.store).TOLERANCE_FOR_LATE_PAYMENTS
                return And(
                    payment_view.status == Payment.STATUS_PENDING,
                    payment_view.due_date < datetime.date.today() -
                                              relativedelta(days=tolerance))
        elif kind == 'category':
            return payment_view.category == value

        raise AssertionError(kind, value)

    def _show_payment_categories(self):
        store = api.new_store()
        self.run_dialog(PaymentCategoryDialog, store, self.payment_category_type)
        self._update_filter_items()
        store.close()

    #
    # Callbacks
    #

    def _on_main_filter__row_separator_func(self, model, titer):
        if model[titer][0] == 'sep':
            return True
        return False

    def _on_main_filter__query_callback(self, state):
        return self._create_main_query(state)

    def _on_results__cell_data_func(self, column, renderer, pv, text):
        if not isinstance(renderer, gtk.CellRendererText):
            return text

        state = self.main_filter.get_state()

        def show_strikethrough():
            if state.value is None:
                return True
            if state.value.value.startswith('category:'):
                return True
            return False

        is_pending = (pv.status == Payment.STATUS_PENDING)
        show_strikethrough = not is_pending and show_strikethrough()
        is_late = pv.is_late()

        renderer.set_property('strikethrough-set', show_strikethrough)
        renderer.set_property('weight-set', is_late)

        if show_strikethrough:
            renderer.set_property('strikethrough', True)
        if is_late:
            renderer.set_property('weight', pango.WEIGHT_BOLD)

        return text

    def on_results__activate_link(self, results, uri):
        if uri.startswith('new_payment'):
            if '?' in uri:
                category = urllib.unquote(uri.split('?', 1)[1])
            else:
                category = None
            self.add_payment(category=category)

    def on_PaymentFlowHistory__activate(self, action):
        self.run_dialog(PaymentFlowHistoryDialog, self.store)

    def on_PaymentCategories__activate(self, action):
        self._show_payment_categories()