class MaintenanceApp(ShellApp): """Maintenance app""" app_title = _(u'Maintenance') gladefile = 'maintenance' search_spec = WorkOrderView search_label = _(u'matching:') report_table = WorkOrdersReport _query_mapper = { 'pending': Or(WorkOrder.status == WorkOrder.STATUS_OPENED, WorkOrder.status == WorkOrder.STATUS_APPROVED), 'in-progress': WorkOrder.status == WorkOrder.STATUS_WORK_IN_PROGRESS, 'in-transport': Eq(WorkOrder.current_branch_id, None), '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")), ("SendOrders", None, _(u"Send orders...")), ("ReceiveOrders", None, _(u"Receive orders...")), # 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")), ("Clients", STOQ_CLIENTS, _(u"Clients..."), group.get("search_clients")), # 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") radio_actions = [ ('ViewKanban', '', _("View as Kanban"), '', _("Show in Kanban mode")), ('ViewList', '', _("View as List"), '', _("Show in list mode")), ] self.add_ui_actions('', radio_actions, 'RadioActions', 'radio') if is_developer_mode(): self.ViewList.props.active = True else: self.ViewList.props.visible = False self.ViewKanban.props.visible = False 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.enable_lazy_search() self.window.add_new_items([ self.NewOrder, ]) self.window.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.window.NewToolItem.set_tooltip(_(u"Create a new work order")) self.window.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) 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) executer = self.search.get_query_executer() 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 [ IdentifierColumn('identifier'), 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('branch_name', title=_(u'Branch'), data_type=str, visible=False), SearchColumn('current_branch_name', title=_(u'Current branch'), data_type=str, visible=False), 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._query_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, select_item=None): self.search.refresh() if select_item is not None: item = self.store.find(WorkOrderView, id=select_item.id).one() self.search.select(item) self._update_list_aware_view() def _update_list_aware_view(self): selection = self.search.get_selected_item() 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'In transport'), 'status:in-transport'), _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: work_order = self.run_dialog(WorkOrderEditor, store, category=store.fetch(category)) if store.committed: self._update_view(select_item=work_order) # A category may have been created on the editor self._update_filters() def _edit_order(self, work_order=None): if work_order is None: work_order = self.search.get_selected_item().work_order with api.trans() as store: self.run_dialog(WorkOrderEditor, store, model=store.fetch(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 not yesno( _(u"This will finish the selected order, marking the " u"work as done. Are you sure?"), gtk.RESPONSE_NO, _(u"Finish order"), _(u"Don't finish")): return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.finish() self._update_view() def _cancel_order(self): if not yesno(_(u"This will cancel the selected order. Are you sure?"), gtk.RESPONSE_NO, _(u"Cancel order"), _(u"Don't cancel")): return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.cancel() self._update_view() def _send_orders(self): with api.trans() as store: self.run_dialog(WorkOrderPackageSendEditor, store) if store.committed: self._update_view() def _receive_orders(self): with api.trans() as store: self.run_dialog(WorkOrderPackageReceiveWizard, store) if store.committed: self._update_view() def _run_order_details_dialog(self): selection = self.search.get_selected_item() 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_search__result_item_popup_menu(self, search, item, event): self.popup.popup(None, None, None, event.button, event.time) def on_search__result_item_activated(self, search, item): if self.Edit.get_sensitive(): self._edit_order() elif self.Details.get_sensitive(): self._run_order_details_dialog() else: assert False def on_search__result_selection_changed(self, search): 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_SendOrders__activate(self, action): self._send_orders() def on_ReceiveOrders__activate(self, action): self._receive_orders() 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.search.get_selected_item() print_report(WorkOrderQuoteReport, workorderview.work_order) def on_PrintReceipt__activate(self, action): workorderview = self.search.get_selected_item() 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() def on_Clients__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True) def on_ViewList__toggled(self, action): if not action.get_active(): return self.search.set_result_view(SearchResultListView, refresh=True) self._update_list_aware_view() def on_ViewKanban__toggled(self, action): if not action.get_active(): return self.search.set_result_view(WorkOrderResultKanbanView, refresh=True) self._update_list_aware_view()
class MaintenanceApp(ShellApp): """Maintenance app""" app_title = _(u'Maintenance') gladefile = 'maintenance' search_spec = WorkOrderView search_label = _(u'matching:') report_table = WorkOrdersReport _status_query_mapper = { 'pending': Or(WorkOrder.status == WorkOrder.STATUS_OPENED, WorkOrder.status == WorkOrder.STATUS_WORK_WAITING), 'in-progress': WorkOrder.status == WorkOrder.STATUS_WORK_IN_PROGRESS, 'finished': WorkOrder.status == WorkOrder.STATUS_WORK_FINISHED, 'delivered': Or(WorkOrder.status == WorkOrder.STATUS_CANCELLED, WorkOrder.status == WorkOrder.STATUS_DELIVERED), } _flags_query_mapper = { 'approved': And(WorkOrder.status != WorkOrder.STATUS_OPENED, WorkOrder.status != WorkOrder.STATUS_CANCELLED), 'in-transport': Eq(WorkOrder.current_branch_id, None), 'rejected': Eq(WorkOrder.is_rejected, True), } # # 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")), ("SendOrders", None, _(u"Send orders...")), ("ReceiveOrders", None, _(u"Receive orders...")), # 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")), ("Clients", STOQ_CLIENTS, _(u"Clients..."), group.get("search_clients")), # 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")), ("DeliverOrder", None, _(u"Delivered...")), ("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")), ("Approve", None, _(u"Approve...")), ("Pause", None, _(u"Pause the work...")), ("Work", None, _(u"Start the work...")), ("Reject", None, _(u"Reject order...")), ("UndoRejection", None, _(u"Undo order rejection...")), ("Reopen", None, _(u"Reopen order...")), ] self.maintenance_ui = self.add_ui_actions("", actions, filename="maintenance.xml") radio_actions = [ ('ViewKanban', '', _("View as Kanban"), '', _("Show in Kanban mode")), ('ViewList', '', _("View as List"), '', _("Show in list mode")), ] self.add_ui_actions('', radio_actions, 'RadioActions', 'radio') if is_developer_mode(): self.ViewList.props.active = True else: self.ViewList.props.visible = False self.ViewKanban.props.visible = False 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.enable_lazy_search() self.window.add_new_items([ self.NewOrder, ]) self.window.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, refresh=True): self.window.NewToolItem.set_tooltip( _(u"Create a new work order")) self.window.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.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 == 'delivered': base_msg = _(u"No delivered 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) def create_filters(self): self.set_text_field_columns(['equipment', 'client_name', 'identifier_str', 'sale_identifier_str']) 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) executer = self.search.get_query_executer() 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 [ IdentifierColumn('identifier', sorted=True), IdentifierColumn('sale_identifier', title=_("Sale #"), visible=False), 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), Column('flag_icon', title=_(u'Equipment'), column='equipment', data_type=gtk.gdk.Pixbuf, format_func=self._format_state_icon, format_func_data=True), SearchColumn('client_name', title=_(u'Client'), data_type=str), SearchColumn('branch_name', title=_(u'Branch'), data_type=str, visible=False), SearchColumn('current_branch_name', title=_(u'Current branch'), data_type=str, visible=False), SearchColumn('execution_branch_name', title=_(u'Execution branch'), data_type=str, visible=False), 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('estimated_start', title=_(u'Estimated start'), data_type=datetime.date, visible=False), SearchColumn('estimated_finish', title=_(u'Estimated finish'), 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 _format_state_icon(self, item, data): # This happens with lazy object lists. Sometimes it calls this function # without actually having the real object. if not isinstance(item, WorkOrderView): return stock_id, tooltip = get_workorder_state_icon(item.work_order) if stock_id is not None: # We are using self.results because render_icon is a gtk.Widget's # method. It has nothing to do with results tough. return self.results.render_icon(stock_id, gtk.ICON_SIZE_MENU) 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_query_mapper[value] if kind == 'flag': return self._flags_query_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, select_item=None): self.refresh() if select_item is not None: item = self.store.find(WorkOrderView, id=select_item.id).one() self.select_result(item) self._update_list_aware_view() def _update_list_aware_view(self): selection = self.search.get_selected_item() has_selected = bool(selection) wo = has_selected and selection.work_order has_quote = has_selected and bool(wo.defect_detected) can_edit = (has_selected and (wo.can_approve() or wo.can_work() or wo.can_finish())) self.set_sensitive([self.Edit], can_edit) self.set_sensitive([self.Details], has_selected) self.set_sensitive([self.Finish], has_selected and wo.can_finish()) self.set_sensitive([self.Cancel], has_selected and wo.can_cancel()) self.set_sensitive([self.PrintReceipt], has_selected and wo.is_finished()) self.set_sensitive([self.PrintQuote], has_quote) for widget, value in [ (self.Approve, has_selected and wo.can_approve()), (self.Reject, has_selected and wo.can_reject()), (self.UndoRejection, has_selected and wo.can_undo_rejection()), (self.Pause, has_selected and wo.can_pause()), (self.Work, has_selected and wo.can_work()), (self.Reopen, has_selected and wo.can_reopen()), # DeliverOrder is grouped here since it's a special case. Only # finished orders without items can be delivered here, so avoid # showing the option if it's not sensitive to avoid confusions (self.DeliverOrder, (has_selected and wo.can_close() and not wo.order_items.count()))]: self.set_sensitive([widget], value) # Some of those options are mutually exclusive (except Approve, # but it can only be called once) so avoid confusions and # hide not available options widget.set_visible(value) def _update_filters(self): options = [ _FilterItem(_(u'Pending'), 'status:pending'), _FilterItem(_(u'In progress'), 'status:in-progress'), _FilterItem(_(u'Finished'), 'status:finished'), _FilterItem(_(u'Delivered or cancelled'), 'status:delivered'), _FilterItem('sep', 'sep'), _FilterItem(_(u'Approved'), 'flag:approved'), _FilterItem(_(u'In transport'), 'flag:in-transport'), _FilterItem(_(u'Rejected'), 'flag:rejected'), ] 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: work_order = self.run_dialog(WorkOrderEditor, store, category=store.fetch(category)) if store.committed: self._update_view(select_item=work_order) # A category may have been created on the editor self._update_filters() def _edit_order(self, work_order=None): if work_order is None: work_order = self.search.get_selected_item().work_order with api.trans() as store: self.run_dialog(WorkOrderEditor, store, model=store.fetch(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 not yesno(_(u"This will finish the selected order, marking the " u"work as done. Are you sure?"), gtk.RESPONSE_NO, _(u"Finish order"), _(u"Don't finish")): return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.finish() self._update_view() def _cancel_order(self): msg_text = _(u"This will cancel the selected order. Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.cancel(reason=rv.notes) self._update_view() def _close_order(self): if not yesno(_(u"This will mark the order as delivered. Are you " "sure?"), gtk.RESPONSE_NO, _(u"Mark as delivered"), _(u"Don't mark")): return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.close() self._update_view(select_item=selection) def _approve_order(self): if not yesno(_(u"This will inform the order that the client has " u"approved the work. Are you sure?"), gtk.RESPONSE_NO, _(u"Approve"), _(u"Don't approve")): return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.approve() self._update_view(select_item=selection) def _pause_order(self): msg_text = _(u"This will inform the order that we are waiting. " u"Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.pause(reason=rv.notes) self._update_view(select_item=selection) def _work(self): selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.work() self._update_view(select_item=selection) def _reject(self): msg_text = _(u"This will reject the order. Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.reject(reason=rv.notes) self._update_view(select_item=selection) def _undo_rejection(self): msg_text = _(u"This will undo the rejection of the order. " u"Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=False) if not rv: return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.undo_rejection(reason=rv.notes) self._update_view(select_item=selection) def _reopen(self): msg_text = _(u"This will reopen the order. Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.trans() as store: work_order = store.fetch(selection.work_order) work_order.reopen(reason=rv.notes) self._update_view(select_item=selection) def _send_orders(self): with api.trans() as store: self.run_dialog(WorkOrderPackageSendEditor, store) if store.committed: self._update_view() def _receive_orders(self): with api.trans() as store: self.run_dialog(WorkOrderPackageReceiveWizard, store) if store.committed: self._update_view() def _run_order_details_dialog(self): selection = self.search.get_selected_item() with api.trans() as store: self.run_dialog(WorkOrderEditor, store, model=store.fetch(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() def _run_notes_editor(self, msg_text, mandatory): return self.run_dialog(NoteEditor, self.store, model=Note(), message_text=msg_text, label_text=_(u"Reason"), mandatory=mandatory) # # Kiwi Callbacks # def _on_main_filter__row_separator_func(self, model, titer): obj = model[titer][1] if obj and obj.value == '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_delivered = work_order.status in [WorkOrder.STATUS_CANCELLED, WorkOrder.STATUS_DELIVERED] is_late = work_order.is_late() for prop, is_set, value in [ ('strikethrough', is_delivered, 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_search__result_item_popup_menu(self, search, item, event): self.popup.popup(None, None, None, event.button, event.time) def on_search__result_item_activated(self, search, item): if self.Edit.get_sensitive(): self._edit_order() elif self.Details.get_sensitive(): self._run_order_details_dialog() else: assert False def on_search__result_selection_changed(self, search): 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_SendOrders__activate(self, action): self._send_orders() def on_ReceiveOrders__activate(self, action): self._receive_orders() 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_Approve__activate(self, action): self._approve_order() def on_Pause__activate(self, action): self._pause_order() def on_Work__activate(self, action): self._work() def on_Reject__activate(self, action): self._reject() def on_UndoRejection__activate(self, action): self._undo_rejection() def on_Reopen__activate(self, action): self._reopen() def on_DeliverOrder__activate(self, action): self._close_order() def on_PrintQuote__activate(self, action): workorderview = self.search.get_selected_item() print_report(WorkOrderQuoteReport, workorderview.work_order) def on_PrintReceipt__activate(self, action): workorderview = self.search.get_selected_item() 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() def on_Clients__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True) def on_ViewList__toggled(self, action): if not action.get_active(): return self.search.set_result_view(SearchResultListView, refresh=True) self._update_list_aware_view() def on_ViewKanban__toggled(self, action): if not action.get_active(): return self.search.set_result_view(WorkOrderResultKanbanView, refresh=True) self._update_list_aware_view()
class BaseAccountWindow(ShellApp): # # Application # def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.results.set_selection_mode(Gtk.SelectionMode.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_spec == InPaymentView: msg = _("No payments to receive found.") else: msg = _("No payments to pay found.") elif v.startswith('category:'): category = v.split(':')[1] 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.parse.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.new_store() as store: self.run_dialog(self.editor_class, store, category=category) if store.committed: self._update_filter_items() self.refresh() self.select_result(self.store.find(self.search_spec, 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.new_store() 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.new_store() 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.new_store() 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.new_store() 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() executer = self.search.get_query_executer() executer.add_filter_query_callback( self.main_filter, self._create_main_query) self.add_filter(self.main_filter, SearchFilterPosition.TOP) self.create_branch_filter(column=self.search_spec.branch_id) 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_spec 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.get_int('TOLERANCE_FOR_LATE_PAYMENTS') return And( payment_view.status == Payment.STATUS_PENDING, payment_view.due_date < localtoday() - 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_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.parse.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()
class ServicesApp(ShellApp): """Services app""" app_title = _(u'Services') gladefile = 'services' search_spec = WorkOrderView search_label = _(u'matching:') report_table = WorkOrdersReport _status_query_mapper = { 'pending': Or(WorkOrder.status == WorkOrder.STATUS_OPENED, WorkOrder.status == WorkOrder.STATUS_WORK_WAITING), 'in-progress': WorkOrder.status == WorkOrder.STATUS_WORK_IN_PROGRESS, 'finished': WorkOrder.status == WorkOrder.STATUS_WORK_FINISHED, 'delivered': WorkOrder.status == WorkOrder.STATUS_DELIVERED, 'cancelled': WorkOrder.status == WorkOrder.STATUS_CANCELLED, 'all-orders': None, 'not-delivered': And(WorkOrder.status != WorkOrder.STATUS_CANCELLED, WorkOrder.status != WorkOrder.STATUS_DELIVERED), } _flags_query_mapper = { 'approved': And(WorkOrder.status != WorkOrder.STATUS_OPENED, WorkOrder.status != WorkOrder.STATUS_CANCELLED), 'in-transport': Eq(WorkOrder.current_branch_id, None), 'rejected': Eq(WorkOrder.is_rejected, True), } def __init__(self, *args, **kwargs): self._other_kinds = {} super(ServicesApp, self).__init__(*args, **kwargs) # # Application # def create_actions(self): group = get_accels('app.services') actions = [ # File ("OrderMenu", None, _(u"Order")), ("NewOrder", None, _(u"Work order..."), group.get("new_order")), ("SendOrders", None, _(u"Send orders...")), ("ReceiveOrders", None, _(u"Receive orders...")), # 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")), ("Clients", STOQ_CLIENTS, _(u"Clients..."), group.get("search_clients")), # 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")), ("DeliverOrder", None, _(u"Deliver...")), ("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")), ("Approve", None, _(u"Approve...")), ("Pause", None, _(u"Pause the work...")), ("Work", None, _(u"Start the work...")), ("Reject", None, _(u"Reject order...")), ("UndoRejection", None, _(u"Undo order rejection...")), ("Reopen", None, _(u"Reopen order...")), ] self.services_ui = self.add_ui_actions("", actions, filename="services.xml") radio_actions = [ ('ViewKanban', '', _("View as Kanban"), '', _("Show in Kanban mode")), ('ViewList', '', _("View as List"), '', _("Show in list mode")), ] self.add_ui_actions('', radio_actions, 'RadioActions', 'radio') 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"Services help"), 'app-services') self.popup = self.uimanager.get_widget('/ServicesSelection') def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.window.add_new_items([ self.NewOrder, ]) self.window.add_search_items([ self.Products, self.Services, self.Categories, ]) self.ViewList.props.active = True 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, refresh=True): self.check_open_inventory() self.window.NewToolItem.set_tooltip( _(u"Create a new work order")) self.window.SearchToolItem.set_tooltip( _(u"Search for work order categories")) if refresh: self._update_view() self.search.focus_search_entry() def deactivate(self): self.uimanager.remove_ui(self.services_ui) 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 == 'delivered': base_msg = _(u"No delivered 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) def create_filters(self): self.set_text_field_columns(['sellable', 'description', 'client_name', 'identifier_str', 'sale_identifier_str']) 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.add_filter(self.main_filter, SearchFilterPosition.TOP, callback=self._get_main_query) self.create_branch_filter(column=[WorkOrder.branch_id, WorkOrder.current_branch_id]) self._update_filters() def get_columns(self): return [ IdentifierColumn('identifier', title=_('WO #'), sorted=True), IdentifierColumn('sale_identifier', title=_("Sale #"), visible=False), SearchColumn('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, multiple_selection=True, search_attribute='category_id', valid_values=self._get_category_values()), Column('equipment', title=_(u'Equipment (Description)'), data_type=str, expand=True, pack_end=True), Column('category_color', title=_(u'Equipment (Description)'), column='equipment', data_type=GdkPixbuf.Pixbuf, format_func=render_pixbuf), Column('flag_icon', title=_(u'Equipment (Description)'), column='equipment', data_type=GdkPixbuf.Pixbuf, format_func=self._format_state_icon, format_func_data=True), SearchColumn('client_name', title=_(u'Client'), data_type=str), SearchColumn('branch_name', title=_(u'Branch'), data_type=str, visible=False), SearchColumn('current_branch_name', title=_(u'Current branch'), data_type=str, visible=False), SearchColumn('execution_branch_name', title=_(u'Execution branch'), data_type=str, visible=False), SearchColumn('supplier_order', title=_("Supplier Order #"), visible=False, 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('estimated_start', title=_(u'Estimated start'), data_type=datetime.date, visible=False), SearchColumn('estimated_finish', title=_(u'Estimated finish'), 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), ] def set_open_inventory(self): # This needs to be implemented because we are calling check_open_inventory. # We won't do anything here tough, WorkOrderEditor will, but we call # check_open_inventory to display the open inventory bar and make # it explicit for the user that there's an open inventory pass def search_for_date(self, date): self.main_filter.combo.select(self._not_delivered_filter_item) dfilter = DateSearchFilter(_("Estimated finish")) dfilter.set_removable() dfilter.select(data=DateSearchFilter.Type.USER_DAY) self.add_filter(dfilter, columns=["estimated_finish"]) dfilter.start_date.set_date(date) self.refresh() def add_filters(self, filter_items, kind, mapper): """Add additional filter option. :param filter_items: list of tuple (name, value, color) :param kind:the kind of filter :param mapper: a dictionary containing the query for each option """ for item in filter_items: option = _FilterItem(item[0], item[1], color=item[2]) self.main_filter.combo.append_item(option.name, option) self._other_kinds[kind] = mapper def new_order(self, category=None, available_categories=None): with api.new_store() as store: work_order = self.run_dialog(WorkOrderEditor, store, category=store.fetch(category), available_categories=available_categories) if store.committed: self._update_view(select_item=work_order) # A category may have been created on the editor self._update_filters() # # Private # def _format_state_icon(self, item, data): # This happens with lazy object lists. Sometimes it calls this function # without actually having the real object. if not isinstance(item, WorkOrderView): return stock_id, tooltip = get_workorder_state_icon(item.work_order) if stock_id is not None: # We are using self.results because render_icon is a Gtk.Widget's # method. It has nothing to do with results tough. return self.results.render_icon(stock_id, Gtk.IconSize.MENU) def _get_main_query(self, state): item = state.value kind, value = item.value.split(':') if kind in self._other_kinds: return self._other_kinds[kind][value] elif kind == 'category': return WorkOrder.category_id == item.id elif kind == 'status': return self._status_query_mapper[value] elif kind == 'flag': return self._flags_query_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 _get_category_values(self): return [ (category.name, category.id, render_pixbuf(category.color)) for category in self.store.find(WorkOrderCategory)] def _update_view(self, select_item=None): self.refresh() if select_item is not None: item = self.store.find(WorkOrderView, id=select_item.id).one() self.select_result(item) self._update_list_aware_view() def _update_list_aware_view(self): selection = self.search.get_selected_item() has_selected = bool(selection) wo = has_selected and selection.work_order if wo and wo.sale is not None: has_quote = wo.order_items.count() > 0 else: has_quote = wo and bool(wo.defect_reported or wo.defect_detected) self.set_sensitive([self.Edit], has_selected and wo.can_edit()) self.set_sensitive([self.Details], has_selected) self.set_sensitive([self.Finish], has_selected and (wo.can_finish() or wo.can_close())) self.set_sensitive([self.Cancel], has_selected and wo.can_cancel()) self.set_sensitive([self.PrintReceipt], has_selected and wo.is_finished()) self.set_sensitive([self.PrintQuote], has_quote) self.Finish.set_short_label(_(u"Finish")) # If the selected work order is already finished, we change the finish # button's label. if has_selected and wo.status == WorkOrder.STATUS_WORK_FINISHED: self.Finish.set_short_label(_(u"Deliver")) for widget, value in [ (self.Approve, has_selected and wo.can_approve()), (self.Reject, has_selected and wo.can_reject()), (self.UndoRejection, has_selected and wo.can_undo_rejection()), (self.Pause, has_selected and wo.can_pause()), (self.Work, has_selected and wo.can_work()), (self.Reopen, has_selected and wo.can_reopen()), # DeliverOrder is grouped here since it's a special case # Only finished orders without items and without sale can be # delivered here, so avoid showing the option if it's not #sensitive to avoid confusions (self.DeliverOrder, (has_selected and wo.can_close() and not wo.order_items.count() and not wo.sale))]: self.set_sensitive([widget], value) # Some of those options are mutually exclusive (except Approve, # but it can only be called once) so avoid confusions and # hide not available options widget.set_visible(value) def _update_filters(self): self._not_delivered_filter_item = _FilterItem(_(u'Not delivered'), 'status:not-delivered') options = [ self._not_delivered_filter_item, _FilterItem(_(u'Pending'), 'status:pending'), _FilterItem(_(u'In progress'), 'status:in-progress'), _FilterItem(_(u'Finished'), 'status:finished'), _FilterItem(_(u'Delivered'), 'status:delivered'), _FilterItem(_(u'Cancelled'), 'status:cancelled'), _FilterItem(_(u'All work orders'), 'status:all-orders'), _FilterItem('sep', 'sep'), _FilterItem(_(u'Approved'), 'flag:approved'), _FilterItem(_(u'In transport'), 'flag:in-transport'), _FilterItem(_(u'Rejected'), 'flag:rejected'), ] 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( [(item.name, item) for item in options]) def _edit_order(self, work_order=None): if work_order is None: work_order = self.search.get_selected_item().work_order with api.new_store() as store: self.run_dialog(WorkOrderEditor, store, model=store.fetch(work_order)) if store.committed: self._update_view() # A category may have been created on the editor self._update_filters() def _finish_or_deliver_order(self): work_order = self.search.get_selected_item().work_order if work_order.status == WorkOrder.STATUS_WORK_FINISHED: self._close_order() else: self._finish_order() def _finish_order(self): work_order = self.search.get_selected_item().work_order if work_order.is_items_totally_reserved(): msg = _(u"This will finish the selected order, marking the " u"work as done. Are you sure?") else: msg = _(u"Some items on this work order are not fully reserved. " u"Do you still want to mark it as finished?") if not yesno(msg, Gtk.ResponseType.NO, _(u"Finish order"), _(u"Don't finish")): return with api.new_store() as store: work_order = store.fetch(work_order) work_order.finish() self._update_view() def _cancel_order(self): rv = self._run_notes_editor(msg_text=cancel_question, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.cancel(reason=rv.notes) self._update_view() def _close_order(self): if not yesno(_(u"This will mark the order as delivered. Are you " "sure?"), Gtk.ResponseType.NO, _(u"Mark as delivered"), _(u"Don't mark")): return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.close() self._update_view(select_item=selection) def _approve_order(self): if not yesno(_(u"This will inform the order that the client has " u"approved the work. Are you sure?"), Gtk.ResponseType.NO, _(u"Approve"), _(u"Don't approve")): return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.approve() self._update_view(select_item=selection) def _pause_order(self): rv = self._run_notes_editor(msg_text=waiting_question, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.pause(reason=rv.notes) self._update_view(select_item=selection) def _work(self): selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.work() self._update_view(select_item=selection) def _reject(self): msg_text = _(u"This will reject the order. Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.reject(reason=rv.notes) self._update_view(select_item=selection) def _undo_rejection(self): msg_text = _(u"This will undo the rejection of the order. " u"Are you sure?") rv = self._run_notes_editor(msg_text=msg_text, mandatory=False) if not rv: return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.undo_rejection(reason=rv.notes) self._update_view(select_item=selection) def _reopen(self): rv = self._run_notes_editor(msg_text=reopen_question, mandatory=True) if not rv: return selection = self.search.get_selected_item() with api.new_store() as store: work_order = store.fetch(selection.work_order) work_order.reopen(reason=rv.notes) self._update_view(select_item=selection) def _send_orders(self): with api.new_store() as store: self.run_dialog(WorkOrderPackageSendEditor, store) if store.committed: self._update_view() def _receive_orders(self): with api.new_store() as store: self.run_dialog(WorkOrderPackageReceiveWizard, store) if store.committed: self._update_view() def _run_order_details_dialog(self): selection = self.search.get_selected_item() with api.new_store() as store: self.run_dialog(WorkOrderEditor, store, model=store.fetch(selection.work_order), visual_mode=True) def _run_order_category_dialog(self): with api.new_store() as store: self.run_dialog(WorkOrderCategoryDialog, store) self._update_view() self._update_filters() def _run_notes_editor(self, msg_text, mandatory): return self.run_dialog(NoteEditor, self.store, model=Note(), message_text=msg_text, label_text=_(u"Reason"), mandatory=mandatory) # # Kiwi Callbacks # def _on_main_filter__row_separator_func(self, model, titer): obj = model[titer][1] if obj and obj.value == 'sep': return True return False 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_delivered = work_order.status in [WorkOrder.STATUS_CANCELLED, WorkOrder.STATUS_DELIVERED] is_late = work_order.is_late() for prop, is_set, value in [ ('strikethrough', is_delivered, 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_search__result_item_popup_menu(self, search, item, event): self.popup.popup(None, None, None, None, event.button.button, event.time) def on_search__result_item_activated(self, search, item): if self.Edit.get_sensitive(): self._edit_order() elif self.Details.get_sensitive(): self._run_order_details_dialog() else: assert False def on_search__result_selection_changed(self, search): 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_SendOrders__activate(self, action): self._send_orders() def on_ReceiveOrders__activate(self, action): self._receive_orders() def on_Edit__activate(self, action): self._edit_order() def on_Finish__activate(self, action): self._finish_or_deliver_order() def on_Cancel__activate(self, action): self._cancel_order() def on_Details__activate(self, action): self._run_order_details_dialog() def on_Approve__activate(self, action): self._approve_order() def on_Pause__activate(self, action): self._pause_order() def on_Work__activate(self, action): self._work() def on_Reject__activate(self, action): self._reject() def on_UndoRejection__activate(self, action): self._undo_rejection() def on_Reopen__activate(self, action): self._reopen() def on_DeliverOrder__activate(self, action): self._close_order() def on_PrintQuote__activate(self, action): workorderview = self.search.get_selected_item() print_report(WorkOrderQuoteReport, workorderview.work_order) def on_PrintReceipt__activate(self, action): workorderview = self.search.get_selected_item() 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() def on_Clients__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True) def on_ViewList__toggled(self, action): if not action.get_active(): return self.search.set_result_view(SearchResultListView, refresh=True) self._update_list_aware_view() def on_ViewKanban__toggled(self, action): if not action.get_active(): return self.search.set_result_view(WorkOrderResultKanbanView, refresh=True) self._update_list_aware_view()
class BaseAccountWindow(ShellApp): # # Application # def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.results.set_selection_mode(Gtk.SelectionMode.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_spec == InPaymentView: msg = _("No payments to receive found.") else: msg = _("No payments to pay found.") elif v.startswith('category:'): category = v.split(':')[1] 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.parse.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.new_store() as store: self.run_dialog(self.editor_class, store, category=category) if store.committed: self._update_filter_items() self.refresh() self.select_result( self.store.find(self.search_spec, 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.new_store() 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.new_store() 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.new_store() 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.new_store() 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() executer = self.search.get_query_executer() executer.add_filter_query_callback(self.main_filter, self._create_main_query) self.add_filter(self.main_filter, SearchFilterPosition.TOP) self.create_branch_filter(column=self.search_spec.branch_id) 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_spec 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.get_int('TOLERANCE_FOR_LATE_PAYMENTS') return And( payment_view.status == Payment.STATUS_PENDING, payment_view.due_date < localtoday() - 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_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.parse.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()
class DeliveryApp(ShellApp): """Delivery app""" app_title = _(u'Deliveries') gladefile = 'delivery' search_spec = DeliveryView search_label = _(u'matching:') # TODO: Create a report for the view here #report_table = DeliveriesReport # # Application # def create_actions(self): #group = get_accels('app.delivery') actions = [ # File ("DeliveryMenu", None, _(u"Delivery")), # Search ( "Transporters", STOQ_TRANSPORTER, _("Transporters..."), # group.get("search_transporters")), None), ( "Clients", STOQ_CLIENTS, _("Clients..."), # group.get("search_clients")), None), ( "Products", None, _("Products..."), # group.get("search_products")), None), ( "Services", None, _("Services..."), # group.get("search_services")), None), # Delivery ( "Edit", gtk.STOCK_EDIT, _("Edit..."), # group.get('delivery_pick'), None, _("Edit the selected delivery")), ( "Pick", None, _("Pick..."), # group.get('delivery_pick'), None, _("Pick the selected delivery")), ( "Pack", None, _("Pack..."), # group.get('delivery_pack'), None, _("Pack the selected delivery")), ( "Send", STOQ_TRANSPORTER, _("Send..."), # group.get('delivery_send'), None, _("Send the selected delivery to deliver")), ( "Receive", gtk.STOCK_APPLY, _("Mark as received..."), # group.get('delivery_receive'), None, _("Mark the selected delivery as received by the client")), ( "Cancel", gtk.STOCK_CANCEL, _("Cancel..."), # group.get('delivery_cancel'), None, _("Cancel the selected delivery")), ] self.delivery_ui = self.add_ui_actions("", actions, filename="delivery.xml") self.set_help_section(_(u"Delivery help"), 'app-delivery') self.popup = self.uimanager.get_widget('/DeliverySelection') def create_ui(self): self.search.enable_lazy_search() # XXX: What should we put on new items? self.window.add_new_items([]) self.window.add_search_items([ self.Products, self.Services, self.Transporters, self.Clients, ]) # FIXME: sale_identifier is here because it needs an integer column. # The lazy summary will actually be taken from the view's # post_search_callback self.search.set_summary_label( column='sale_identifier', label=('<b>%s</b>' % api.escape(_('Number of deliveries:'))), 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, refresh=True): self.check_open_inventory() # XXX #self.window.NewToolItem.set_tooltip( # _("Create a new delivery")) self.window.SearchToolItem.set_tooltip(_("Search for transporters")) if refresh: self._update_view() self.search.focus_search_entry() def deactivate(self): self.uimanager.remove_ui(self.delivery_ui) def new_activate(self): print "FIXME: new_activate" def search_activate(self): self.run_dialog(TransporterSearch, self.store) def search_completed(self, results, states): if len(results): return state = states[1] if state is None: return if state.value is None: # Base search with no filters base_msg = _("No deliveries could be found.") url_msg = '' elif state: base_msg = { Delivery.STATUS_INITIAL: _("No pending deliveries could be found"), Delivery.STATUS_CANCELLED: _("No cancelled deliveries could be found"), Delivery.STATUS_PICKED: _("No picked deliveries could be found"), Delivery.STATUS_PACKED: _("No packed deliveries could be found"), Delivery.STATUS_SENT: _("No sent deliveries could be found"), Delivery.STATUS_RECEIVED: _("No received deliveries could be found"), }[state.value] url_msg = '' msg = '\n\n'.join([base_msg, url_msg]) self.search.set_message(msg) def create_filters(self): self.set_text_field_columns(['client_name', 'sale_identifier']) self.main_filter = ComboSearchFilter(_('Show'), []) self.add_filter(self.main_filter, SearchFilterPosition.TOP, callback=self._get_main_query) self.create_branch_filter(column=[Sale.branch_id]) self._update_filters() def get_columns(self): return [ IdentifierColumn('sale_identifier', title=_("Sale #")), SearchColumn('status_str', title=_(u'Status'), search_attribute='status', data_type=str, valid_values=self._get_status_values()), SearchColumn('client_name', title=_(u'Client'), data_type=str, expand=True), Column('flag_icon', title=_(u'Status (Description)'), column='client_name', data_type=gtk.gdk.Pixbuf, format_func=self._format_state_icon, format_func_data=True), SearchColumn('branch_name', title=_(u'Branch'), data_type=str, visible=False), SearchColumn('transporter_name', title=_(u'Transporter'), data_type=str), SearchColumn('open_date', title=_(u'Open date'), data_type=datetime.date), SearchColumn('cancel_date', title=_(u'Cancel date'), data_type=datetime.date, visible=False), SearchColumn('pick_date', title=_(u'Pick date'), data_type=datetime.date, visible=False), SearchColumn('pack_date', title=_(u'Pack date'), data_type=datetime.date, visible=False), SearchColumn('send_date', title=_(u'Send date'), data_type=datetime.date), SearchColumn('receive_date', title=_(u'Receive date'), data_type=datetime.date, visible=False), ] def set_open_inventory(self): pass # # Private # def _edit(self): delivery = self.search.get_selected_item().delivery with api.new_store() as store: self.run_dialog(DeliveryEditor, store, model=store.fetch(delivery)) if store.committed: self._update_view() def _cancel(self): if not yesno(_("This will cancel the delivery. Are you sure?"), gtk.RESPONSE_NO, _(u"Cancel"), _(u"Don't cancel")): return selection = self.search.get_selected_item() with api.new_store() as store: delivery = store.fetch(selection.delivery) delivery.close() self._update_view(select_item=selection) def _pick(self): if not yesno(_("This will mark the delivery as picked. Are you sure?"), gtk.RESPONSE_NO, _(u"Mark as picked"), _(u"Don't mark")): return selection = self.search.get_selected_item() with api.new_store() as store: delivery = store.fetch(selection.delivery) delivery.pick() self._update_view(select_item=selection) def _pack(self): if not yesno(_("This will mark the delivery as packed. Are you sure?"), gtk.RESPONSE_NO, _(u"Mark as packed"), _(u"Don't mark")): return selection = self.search.get_selected_item() with api.new_store() as store: delivery = store.fetch(selection.delivery) delivery.pack() self._update_view(select_item=selection) def _send(self): if not yesno( _("This will mark the delivery as sent to the client. " "Are you sure?"), gtk.RESPONSE_NO, _(u"Mark as sent"), _(u"Don't mark")): return selection = self.search.get_selected_item() with api.new_store() as store: delivery = store.fetch(selection.delivery) delivery.pack() self._update_view(select_item=selection) def _receive(self): if not yesno( _("This will mark the delivery as received by the client. " "Are you sure?"), gtk.RESPONSE_NO, _(u"Mark as received"), _(u"Don't mark")): return selection = self.search.get_selected_item() with api.new_store() as store: delivery = store.fetch(selection.delivery) delivery.pack() self._update_view(select_item=selection) def _format_state_icon(self, item, data): # This happens with lazy object lists. Sometimes it calls this function # without actually having the real object. if not isinstance(item, DeliveryView): return stock_id, tooltip = get_delivery_state_icon(item.delivery) if stock_id is not None: # We are using self.results because render_icon is a gtk.Widget's # method. It has nothing to do with results tough. return self.results.render_icon(stock_id, gtk.ICON_SIZE_MENU) def _get_main_query(self, state): if state.value is None: return True return Delivery.status == state.value def _get_status_values(self): return ([(_('Any'), None)] + [(v, k) for k, v in Delivery.statuses.items()]) def _update_view(self, select_item=None): self.refresh() if select_item is not None: item = self.store.find(DeliveryView, id=select_item.id).one() self.select_result(item) self._update_list_aware_view() def _update_list_aware_view(self): selection = self.search.get_selected_item() has_selected = bool(selection) delivery = has_selected and selection.delivery self.set_sensitive([self.Edit], has_selected) self.set_sensitive([self.Pick], has_selected and delivery.can_pick()) self.set_sensitive([self.Pack], has_selected and delivery.can_pack()) self.set_sensitive([self.Send], has_selected and delivery.can_send()) self.set_sensitive([self.Receive], has_selected and delivery.can_receive()) self.set_sensitive([self.Cancel], has_selected and delivery.can_cancel()) def _update_filters(self): items = [(_("All Deliveries"), None)] items.extend((status_str, status) for status, status_str in Delivery.statuses.items()) self.main_filter.update_values(items) # # Callbacks # def _on_results__cell_data_func(self, column, renderer, item, text): if not isinstance(renderer, gtk.CellRendererText): return text delivery = item.delivery is_finished = delivery.status in [ Delivery.STATUS_SENT, Delivery.STATUS_RECEIVED ] is_waiting = delivery.status == Delivery.STATUS_INITIAL is_picked = delivery.status == Delivery.STATUS_PICKED for prop, is_set, value in [('strikethrough', is_finished, True), ('style', is_picked, pango.STYLE_ITALIC), ('weight', is_waiting, pango.WEIGHT_BOLD)]: renderer.set_property(prop + '-set', is_set) if is_set: renderer.set_property(prop, value) return text def on_search__result_item_popup_menu(self, search, item, event): self.popup.popup(None, None, None, event.button, event.time) def on_search__result_selection_changed(self, search): self._update_list_aware_view() def on_search__result_item_activated(self, search, item): self._edit() def on_Edit__activate(self, action): self._edit() def on_Cancel__activate(self, action): self._cancel() def on_Pick__activate(self, action): self._pick() def on_Pack__activate(self, action): self._pack() def on_Send__activate(self, action): self._send() def on_Receive__activate(self, action): self._receive() def on_Products__activate(self, action): self.run_dialog(ProductSearch, self.store, hide_footer=True, hide_toolbar=True) def on_Transporters__activate(self, action): self.run_dialog(TransporterSearch, self.store) def on_Services__activate(self, action): self.run_dialog(ServiceSearch, self.store) def on_Clients__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True)
class ServicesApp(ShellApp): """Services app""" app_title = _(u'Services') gladefile = 'services' search_spec = WorkOrderView search_label = _(u'matching:') report_table = WorkOrdersReport _status_query_mapper = { 'pending': Or(WorkOrder.status == WorkOrder.STATUS_OPENED, WorkOrder.status == WorkOrder.STATUS_WORK_WAITING), 'in-progress': WorkOrder.status == WorkOrder.STATUS_WORK_IN_PROGRESS, 'finished': WorkOrder.status == WorkOrder.STATUS_WORK_FINISHED, 'delivered': WorkOrder.status == WorkOrder.STATUS_DELIVERED, 'cancelled': WorkOrder.status == WorkOrder.STATUS_CANCELLED, 'all-orders': None, 'not-delivered': And(WorkOrder.status != WorkOrder.STATUS_CANCELLED, WorkOrder.status != WorkOrder.STATUS_DELIVERED), } _flags_query_mapper = { 'approved': And(WorkOrder.status != WorkOrder.STATUS_OPENED, WorkOrder.status != WorkOrder.STATUS_CANCELLED), 'in-transport': Eq(WorkOrder.current_branch_id, None), 'rejected': Eq(WorkOrder.is_rejected, True), } def __init__(self, *args, **kwargs): self._other_kinds = {} self.actions = WorkOrderActions.get_instance() super(ServicesApp, self).__init__(*args, **kwargs) # # Application # def create_actions(self): group = get_accels('app.services') actions = [ # 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")), ("Clients", None, _(u"Clients..."), group.get("search_clients")), ] self.services_ui = self.add_ui_actions(actions) radio_actions = [ ('ViewKanban', '', _("View as Kanban"), '', _("Show in Kanban mode")), ('ViewList', '', _("View as List"), '', _("Show in list mode")), ] self.add_ui_actions(radio_actions, 'RadioActions') self.set_help_section(_(u"Services help"), 'app-services') def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'work_order.Details', True), ('fa-edit-symbolic', _('Edit'), 'work_order.Edit', True), ('fa-check-symbolic', _('Finish'), 'work_order.FinishOrClose', True), ('fa-ban-symbolic', _('Cancel'), 'work_order.Cancel', True), ('', _('Deliver'), 'work_order.Close', False), # Separator ('', _('Approve'), 'work_order.Approve', False), ('', _('Pause the work'), 'work_order.Pause', False), ('', _('Start the work'), 'work_order.Work', False), ('', _('Reject order'), 'work_order.Reject', False), ('', _('Inform client'), 'work_order.InformClient', False), ('', _('Undo order rejection'), 'work_order.UndoRejection', False), ('', _('Repoen order'), 'work_order.Reopen', False), # Separator ('', _('Print quote'), 'work_order.PrintQuote', False), ('', _('Print receipt'), 'work_order.PrintReceipt', False), ] return options def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.window.add_print_items2([ (_("Print quote..."), 'work_order.PrintQuote'), (_("Print receipt..."), 'work_order.PrintReceipt'), ]) self.window.add_export_items() self.window.add_extra_items2([ (_("Send orders..."), 'work_order.SendOrders'), (_("Receive orders..."), 'work_order.ReceiveOrders'), ]) self.window.add_extra_items([self.ViewKanban, self.ViewList]) self.window.add_new_items2([ (_("Work order..."), 'work_order.NewOrder'), ]) self.window.add_search_items([ self.Products, self.Services, self.Categories, self.Clients, ]) 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, refresh=True): self.check_open_inventory() is_kanban = self.window._current_app_settings.get('show-kanban', False) if is_kanban: self.ViewKanban.set_state(GLib.Variant.new_boolean(True)) self.search.set_result_view(WorkOrderResultKanbanView, refresh=refresh) if refresh: self._update_view() self.search.focus_search_entry() def deactivate(self): # Reset actions to clean up connections self.actions = None 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 == 'delivered': base_msg = _(u"No delivered 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.parse.quote(value), 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) def create_filters(self): self.set_text_field_columns([ 'sellable', 'description', 'client_name', 'identifier_str', 'sale_identifier_str' ]) 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.add_filter(self.main_filter, SearchFilterPosition.TOP, callback=self._get_main_query) self.create_branch_filter( column=[WorkOrder.branch_id, WorkOrder.current_branch_id]) self._update_filters() def get_columns(self): return [ IdentifierColumn('identifier', title=_('WO #'), sorted=True), IdentifierColumn('sale_identifier', title=_("Sale #"), visible=False), SearchColumn('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, multiple_selection=True, search_attribute='category_id', valid_values=self._get_category_values()), Column('equipment', title=_(u'Equipment (Description)'), data_type=str, expand=True, pack_end=True), Column('category_color', title=_(u'Equipment (Description)'), column='equipment', data_type=GdkPixbuf.Pixbuf, format_func=render_pixbuf), Column('flag_icon', title=_(u'Equipment (Description)'), column='equipment', data_type=GdkPixbuf.Pixbuf, format_func=self._format_state_icon, format_func_data=True), SearchColumn('client_name', title=_(u'Client'), data_type=str), SearchColumn('branch_name', title=_(u'Branch'), data_type=str, visible=False), SearchColumn('current_branch_name', title=_(u'Current branch'), data_type=str, visible=False), SearchColumn('execution_branch_name', title=_(u'Execution branch'), data_type=str, visible=False), SearchColumn('supplier_order', title=_("Supplier Order #"), visible=False, 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('estimated_start', title=_(u'Estimated start'), data_type=datetime.date, visible=False), SearchColumn('estimated_finish', title=_(u'Estimated finish'), 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), ] def set_open_inventory(self): # This needs to be implemented because we are calling check_open_inventory. # We won't do anything here tough, WorkOrderEditor will, but we call # check_open_inventory to display the open inventory bar and make # it explicit for the user that there's an open inventory pass def search_for_date(self, date): self.main_filter.combo.select(self._not_delivered_filter_item) dfilter = DateSearchFilter(_("Estimated finish")) dfilter.set_removable() dfilter.select(data=DateSearchFilter.Type.USER_DAY) self.add_filter(dfilter, columns=["estimated_finish"]) dfilter.start_date.set_date(date) self.refresh() def add_filters(self, filter_items, kind, mapper): """Add additional filter option. :param filter_items: list of tuple (name, value, color) :param kind:the kind of filter :param mapper: a dictionary containing the query for each option """ for item in filter_items: option = _FilterItem(item[0], item[1], color=item[2]) self.main_filter.combo.append_item(option.name, option) self._other_kinds[kind] = mapper # # Private # def _format_state_icon(self, item, data): # This happens with lazy object lists. Sometimes it calls this function # without actually having the real object. if not isinstance(item, WorkOrderView): return stock_id, tooltip = get_workorder_state_icon(item.work_order) if stock_id is not None: return render_icon(stock_id, 16) def _get_main_query(self, state): item = state.value kind, value = item.value.split(':') if kind in self._other_kinds: return self._other_kinds[kind][value] elif kind == 'category': return WorkOrder.category_id == item.id elif kind == 'status': return self._status_query_mapper[value] elif kind == 'flag': return self._flags_query_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 _get_category_values(self): return [(category.name, category.id, render_pixbuf(category.color)) for category in self.store.find(WorkOrderCategory)] def _update_view(self, select_item=None): self.refresh() if select_item is not None: item = self.store.find(WorkOrderView, id=select_item.id).one() self.select_result(item) self._update_list_aware_view() def _update_list_aware_view(self): selection = self.search.get_selected_item() wo = selection and selection.work_order self.actions.set_model(wo) finish_btn = self.window.domain_header.get_children()[2] finish_btn.set_tooltip_text(_(u"Finish")) # If the selected work order is already finished, we change the finish # button's label. if wo and wo.status == WorkOrder.STATUS_WORK_FINISHED: finish_btn.set_tooltip_text(_(u"Deliver")) def _update_filters(self): self._not_delivered_filter_item = _FilterItem(_(u'Not delivered'), 'status:not-delivered') options = [ self._not_delivered_filter_item, _FilterItem(_(u'Pending'), 'status:pending'), _FilterItem(_(u'In progress'), 'status:in-progress'), _FilterItem(_(u'Finished'), 'status:finished'), _FilterItem(_(u'Delivered'), 'status:delivered'), _FilterItem(_(u'Cancelled'), 'status:cancelled'), _FilterItem(_(u'All work orders'), 'status:all-orders'), _FilterItem('sep', 'sep'), _FilterItem(_(u'Approved'), 'flag:approved'), _FilterItem(_(u'In transport'), 'flag:in-transport'), _FilterItem(_(u'Rejected'), 'flag:rejected'), ] 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([(item.name, item) for item in options]) def _run_order_category_dialog(self): with api.new_store() 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): obj = model[titer][1] if obj and obj.value == 'sep': return True return False 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_delivered = work_order.status in [ WorkOrder.STATUS_CANCELLED, WorkOrder.STATUS_DELIVERED ] is_late = work_order.is_late() for prop, is_set, value in [('strikethrough', is_delivered, 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_search__result_item_popup_menu(self, search, objectlist, item, event): self._popover.set_relative_to(objectlist) self.show_popover(event) def on_search__result_item_activated(self, search, item): self.actions.edit_or_details(item.work_order) def on_search__result_selection_changed(self, search): 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 = str(urllib.parse.unquote(uri.split('?', 1)[1])) category = self.store.find(WorkOrderCategory, name=category_name).one() else: category = None self.actions.new_order(category=category) def on_actions__model_created(self, actions, order): self._update_view(select_item=order) # A category may have been created on the editor self._update_filters() def on_actions__model_edited(self, actions, order): self._update_view() # A category may have been created on the editor self._update_filters() 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() def on_Clients__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True) def on_ViewList__change_state(self, action, value): action.set_state(value) if not value.get_boolean(): return self.ViewKanban.set_state( GLib.Variant.new_boolean(not value.get_boolean())) self.search.set_result_view(SearchResultListView, refresh=True) self._update_list_aware_view() def on_ViewKanban__change_state(self, action, value): action.set_state(value) self.ViewList.set_state( GLib.Variant.new_boolean(not value.get_boolean())) self.window._current_app_settings['show-kanban'] = value.get_boolean() if not value.get_boolean(): return self.search.set_result_view(WorkOrderResultKanbanView, refresh=True) self._update_list_aware_view()
class ServicesApp(ShellApp): """Services app""" app_title = _(u'Services') gladefile = 'services' search_spec = WorkOrderView search_label = _(u'matching:') report_table = WorkOrdersReport _status_query_mapper = { 'pending': Or(WorkOrder.status == WorkOrder.STATUS_OPENED, WorkOrder.status == WorkOrder.STATUS_WORK_WAITING), 'in-progress': WorkOrder.status == WorkOrder.STATUS_WORK_IN_PROGRESS, 'finished': WorkOrder.status == WorkOrder.STATUS_WORK_FINISHED, 'delivered': WorkOrder.status == WorkOrder.STATUS_DELIVERED, 'cancelled': WorkOrder.status == WorkOrder.STATUS_CANCELLED, 'all-orders': None, 'not-delivered': And(WorkOrder.status != WorkOrder.STATUS_CANCELLED, WorkOrder.status != WorkOrder.STATUS_DELIVERED), } _flags_query_mapper = { 'approved': And(WorkOrder.status != WorkOrder.STATUS_OPENED, WorkOrder.status != WorkOrder.STATUS_CANCELLED), 'in-transport': Eq(WorkOrder.current_branch_id, None), 'rejected': Eq(WorkOrder.is_rejected, True), } def __init__(self, *args, **kwargs): self._other_kinds = {} self.actions = WorkOrderActions.get_instance() super(ServicesApp, self).__init__(*args, **kwargs) # # Application # def create_actions(self): group = get_accels('app.services') actions = [ # 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")), ("Clients", None, _(u"Clients..."), group.get("search_clients")), ] self.services_ui = self.add_ui_actions(actions) radio_actions = [ ('ViewKanban', '', _("View as Kanban"), '', _("Show in Kanban mode")), ('ViewList', '', _("View as List"), '', _("Show in list mode")), ] self.add_ui_actions(radio_actions, 'RadioActions') self.set_help_section(_(u"Services help"), 'app-services') def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'work_order.Details', True), ('fa-edit-symbolic', _('Edit'), 'work_order.Edit', True), ('fa-check-symbolic', _('Finish'), 'work_order.FinishOrClose', True), ('fa-ban-symbolic', _('Cancel'), 'work_order.Cancel', True), ('', _('Deliver'), 'work_order.Close', False), # Separator ('', _('Approve'), 'work_order.Approve', False), ('', _('Pause the work'), 'work_order.Pause', False), ('', _('Start the work'), 'work_order.Work', False), ('', _('Reject order'), 'work_order.Reject', False), ('', _('Check order'), 'work_order.CheckOrder', False), ('', _('Inform client'), 'work_order.InformClient', False), ('', _('Undo order rejection'), 'work_order.UndoRejection', False), ('', _('Repoen order'), 'work_order.Reopen', False), # Separator ('', _('Print quote'), 'work_order.PrintQuote', False), ('', _('Print receipt'), 'work_order.PrintReceipt', False), ] return options def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.window.add_print_items2([ (_("Print quote..."), 'work_order.PrintQuote'), (_("Print receipt..."), 'work_order.PrintReceipt'), ]) self.window.add_export_items() self.window.add_extra_items2([ (_("Send orders..."), 'work_order.SendOrders'), (_("Receive orders..."), 'work_order.ReceiveOrders'), ]) self.window.add_extra_items([self.ViewKanban, self.ViewList]) self.window.add_new_items2([ (_("Work order..."), 'work_order.NewOrder'), ]) self.window.add_search_items([ self.Products, self.Services, self.Categories, self.Clients, ]) 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, refresh=True): self.check_open_inventory() is_kanban = self.window._current_app_settings.get('show-kanban', False) if is_kanban: self.ViewKanban.set_state(GLib.Variant.new_boolean(True)) self.search.set_result_view(WorkOrderResultKanbanView, refresh=refresh) if refresh: self._update_view() self.search.focus_search_entry() def deactivate(self): # Reset actions to clean up connections self.actions = None 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 == 'delivered': base_msg = _(u"No delivered 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.parse.quote(value), 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) def create_filters(self): self.set_text_field_columns(['sellable', 'description', 'client_name', 'identifier_str', 'sale_identifier_str']) 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.add_filter(self.main_filter, SearchFilterPosition.TOP, callback=self._get_main_query) self.create_branch_filter(column=[WorkOrder.branch_id, WorkOrder.current_branch_id]) self._update_filters() def get_columns(self): return [ IdentifierColumn('identifier', title=_('WO #'), sorted=True), IdentifierColumn('sale_identifier', title=_("Sale #"), visible=False), SearchColumn('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, multiple_selection=True, search_attribute='category_id', valid_values=self._get_category_values()), Column('equipment', title=_(u'Equipment (Description)'), data_type=str, expand=True, pack_end=True), Column('category_color', title=_(u'Equipment (Description)'), column='equipment', data_type=GdkPixbuf.Pixbuf, format_func=render_pixbuf), Column('flag_icon', title=_(u'Equipment (Description)'), column='equipment', data_type=GdkPixbuf.Pixbuf, format_func=self._format_state_icon, format_func_data=True), SearchColumn('client_name', title=_(u'Client'), data_type=str), SearchColumn('branch_name', title=_(u'Branch'), data_type=str, visible=False), SearchColumn('current_branch_name', title=_(u'Current branch'), data_type=str, visible=False), SearchColumn('execution_branch_name', title=_(u'Execution branch'), data_type=str, visible=False), SearchColumn('supplier_order', title=_("Supplier Order #"), visible=False, 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('estimated_start', title=_(u'Estimated start'), data_type=datetime.date, visible=False), SearchColumn('estimated_finish', title=_(u'Estimated finish'), 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), ] def set_open_inventory(self): # This needs to be implemented because we are calling check_open_inventory. # We won't do anything here tough, WorkOrderEditor will, but we call # check_open_inventory to display the open inventory bar and make # it explicit for the user that there's an open inventory pass def search_for_date(self, date): self.main_filter.combo.select(self._not_delivered_filter_item) dfilter = DateSearchFilter(_("Estimated finish")) dfilter.set_removable() dfilter.select(data=DateSearchFilter.Type.USER_DAY) self.add_filter(dfilter, columns=["estimated_finish"]) dfilter.start_date.set_date(date) self.refresh() def add_filters(self, filter_items, kind, mapper): """Add additional filter option. :param filter_items: list of tuple (name, value, color) :param kind:the kind of filter :param mapper: a dictionary containing the query for each option """ for item in filter_items: option = _FilterItem(item[0], item[1], color=item[2]) self.main_filter.combo.append_item(option.name, option) self._other_kinds[kind] = mapper # # Private # def _format_state_icon(self, item, data): # This happens with lazy object lists. Sometimes it calls this function # without actually having the real object. if not isinstance(item, WorkOrderView): return stock_id, tooltip = get_workorder_state_icon(item.work_order) if stock_id is not None: return render_icon(stock_id, 16) def _get_main_query(self, state): item = state.value kind, value = item.value.split(':') if kind in self._other_kinds: return self._other_kinds[kind][value] elif kind == 'category': return WorkOrder.category_id == item.id elif kind == 'status': return self._status_query_mapper[value] elif kind == 'flag': return self._flags_query_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 _get_category_values(self): return [ (category.name, category.id, render_pixbuf(category.color)) for category in self.store.find(WorkOrderCategory)] def _update_view(self, select_item=None): self.refresh() if select_item is not None: item = self.store.find(WorkOrderView, id=select_item.id).one() self.select_result(item) self._update_list_aware_view() def _update_list_aware_view(self): selection = self.search.get_selected_item() wo = selection and selection.work_order self.actions.set_model(wo) finish_btn = self.window.domain_header.get_children()[2] finish_btn.set_tooltip_text(_(u"Finish")) # If the selected work order is already finished, we change the finish # button's label. if wo and wo.status == WorkOrder.STATUS_WORK_FINISHED: finish_btn.set_tooltip_text(_(u"Deliver")) def _update_filters(self): self._not_delivered_filter_item = _FilterItem(_(u'Not delivered'), 'status:not-delivered') options = [ self._not_delivered_filter_item, _FilterItem(_(u'Pending'), 'status:pending'), _FilterItem(_(u'In progress'), 'status:in-progress'), _FilterItem(_(u'Finished'), 'status:finished'), _FilterItem(_(u'Delivered'), 'status:delivered'), _FilterItem(_(u'Cancelled'), 'status:cancelled'), _FilterItem(_(u'All work orders'), 'status:all-orders'), _FilterItem('sep', 'sep'), _FilterItem(_(u'Approved'), 'flag:approved'), _FilterItem(_(u'In transport'), 'flag:in-transport'), _FilterItem(_(u'Rejected'), 'flag:rejected'), ] 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( [(item.name, item) for item in options]) def _run_order_category_dialog(self): with api.new_store() 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): obj = model[titer][1] if obj and obj.value == 'sep': return True return False 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_delivered = work_order.status in [WorkOrder.STATUS_CANCELLED, WorkOrder.STATUS_DELIVERED] is_late = work_order.is_late() for prop, is_set, value in [ ('strikethrough', is_delivered, 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_search__result_item_popup_menu(self, search, objectlist, item, event): self._popover.set_relative_to(objectlist) self.show_popover(event) def on_search__result_item_activated(self, search, item): self.actions.edit_or_details(item.work_order) def on_search__result_selection_changed(self, search): 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 = str(urllib.parse.unquote(uri.split('?', 1)[1])) category = self.store.find(WorkOrderCategory, name=category_name).one() else: category = None self.actions.new_order(category=category) def on_actions__model_created(self, actions, order): self._update_view(select_item=order) # A category may have been created on the editor self._update_filters() def on_actions__model_edited(self, actions, order): self._update_view() # A category may have been created on the editor self._update_filters() 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() def on_Clients__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True) def on_ViewList__change_state(self, action, value): action.set_state(value) if not value.get_boolean(): return self.ViewKanban.set_state(GLib.Variant.new_boolean(not value.get_boolean())) self.search.set_result_view(SearchResultListView, refresh=True) self._update_list_aware_view() def on_ViewKanban__change_state(self, action, value): action.set_state(value) self.ViewList.set_state(GLib.Variant.new_boolean(not value.get_boolean())) self.window._current_app_settings['show-kanban'] = value.get_boolean() if not value.get_boolean(): return self.search.set_result_view(WorkOrderResultKanbanView, refresh=True) self._update_list_aware_view()