class ProductionItemsSearch(ProductSearch): title = _(u"Production Items") search_spec = ProductionItemView report_class = ProductionItemReport csv_data = None has_print_price_button = False def __init__(self, store, hide_footer=True, hide_toolbar=True): ProductSearch.__init__(self, store, hide_footer=hide_footer, hide_toolbar=hide_toolbar) # # SearchDialog # def create_filters(self): self.set_text_field_columns(["description", "order_identifier_str"]) statuses = [(desc, i) for i, desc in ProductionOrder.statuses.items()] statuses.insert(0, (_(u"Any"), None)) self.status_filter = ComboSearchFilter(_("order status:"), statuses) self.status_filter.select(ProductionOrder.ORDER_PRODUCING) self.add_filter(self.status_filter, columns=["order_status"], position=SearchFilterPosition.TOP) def get_columns(self): return [ IdentifierColumn("order_identifier", title=_(u"Order #"), sorted=True), SearchColumn("category_description", title=_(u"Category"), data_type=str), SearchColumn("description", title=_(u"Description"), data_type=str, expand=True), SearchColumn("unit_description", title=_(u"Unit"), data_type=str), SearchColumn("quantity", title=_(u"To Produce"), data_type=Decimal), SearchColumn("produced", title=_(u"Produced"), data_type=Decimal), SearchColumn("lost", title=_(u"Lost"), data_type=Decimal, visible=False), ]
def create_filters(self): self.set_text_field_columns(["name", "cpf", "rg_number", "phone_number", "mobile_number"]) statuses = [(v, k) for k, v in Client.statuses.items()] statuses.insert(0, (_("Any"), None)) status_filter = ComboSearchFilter(_("Show clients with status"), statuses) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ["status"])
def create_filters(self): items = [(_("Active"), True), (_("Inactive"), False)] items.insert(0, (_("Any"), None)) status_filter = ComboSearchFilter(_("Show transporters with status"), items) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ["is_active"])
def create_filters(self): self.set_text_field_columns(["name", "phone_number"]) items = [(_("Active"), True), (_("Inactive"), False)] items.insert(0, (_("Any"), None)) status_filter = ComboSearchFilter(_("Show transporters with status"), items) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ["is_active"])
def create_filters(self): # status filter statuses = [(desc, i) for i, desc in Loan.statuses.items()] statuses.insert(0, (_(u'Any'), None)) status_filter = ComboSearchFilter(_(u'with status:'), statuses) status_filter.select(None) self.add_filter(status_filter, columns=['loan_status'], position=SearchFilterPosition.TOP)
def create_filters(self): statuses = [(value, key) for key, value in Branch.statuses.items()] statuses.insert(0, (_("Any"), None)) status_filter = ComboSearchFilter(_("Show branches with status"), statuses) status_filter.select(None) executer = self.search.get_query_executer() executer.add_filter_query_callback(status_filter, self._get_status_query) self.search.add_filter(status_filter, SearchFilterPosition.TOP)
def create_filters(self): self.set_text_field_columns(['description', 'barcode']) items = [(v, k) for k, v in Sellable.statuses.items()] items.insert(0, (_('Any'), None)) service_filter = ComboSearchFilter(_('Show services'), items) service_filter.select(None) self.executer.add_query_callback(self._get_query) self.add_filter(service_filter, SearchFilterPosition.TOP, ['status'])
def create_filters(self): self.set_text_field_columns(['description', 'identifier_str']) # status filter statuses = [(desc, i) for i, desc in Loan.statuses.items()] statuses.insert(0, (_(u'Any'), None)) status_filter = ComboSearchFilter(_(u'with status:'), statuses) status_filter.select(None) self.add_filter(status_filter, columns=['loan_status'], position=SearchFilterPosition.TOP)
def create_filters(self): self.set_text_field_columns(['name', 'cpf', 'rg_number', 'phone_number', 'mobile_number']) statuses = [(v, k) for k, v in Client.statuses.items()] statuses.insert(0, (_('Any'), None)) status_filter = ComboSearchFilter(_('Show clients with status'), statuses) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ['status'])
def create_filters(self): self.set_text_field_columns(['name', 'phone_number']) items = [(_('Active'), True), (_('Inactive'), False)] items.insert(0, (_('Any'), None)) status_filter = ComboSearchFilter(_('Show transporters with status'), items) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ['is_active'])
def create_filters(self): self.set_text_field_columns(['name', 'acronym', 'phone_number']) statuses = [(value, key) for key, value in Branch.statuses.items()] statuses.insert(0, (_('Any'), None)) status_filter = ComboSearchFilter(_('Show branches with status'), statuses) status_filter.select(None) self.executer.add_filter_query_callback( status_filter, self._get_status_query) self.search.add_filter(status_filter, SearchFilterPosition.TOP)
class DeliverySearch(SearchEditor): """Delivery search implementation""" title = _('Delivery Search') search_spec = DeliveryView editor_class = DeliveryEditor has_new_button = False size = (750, 450) # # SearchEditor hooks # def _get_status_values(self): items = [(value, key) for key, value in Delivery.statuses.items()] items.insert(0, (_('Any'), None)) return items def create_filters(self): self.set_text_field_columns(['tracking_code', 'transporter_name', 'client_name', 'identifier_str']) # Status statuses = [(desc, st) for st, desc in Delivery.statuses.items()] statuses.insert(0, (_('Any'), None)) self.status_filter = ComboSearchFilter(_('With status:'), statuses) self.status_filter.select(None) self.add_filter(self.status_filter, columns=['status'], position=SearchFilterPosition.TOP) def get_editor_model(self, viewable): return viewable.delivery def get_columns(self): return [IdentifierColumn('sale_identifier', title=_('Sale #'), order=gtk.SORT_DESCENDING), SearchColumn('status_str', title=_('Status'), data_type=str, search_attribute='status', valid_values=self._get_status_values()), Column('address_str', title=_('Address'), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('tracking_code', title=_('Tracking code'), data_type=str), SearchColumn('transporter_name', title=_('Transporter'), data_type=str), SearchColumn('client_name', title=_('Client'), data_type=str), SearchColumn('open_date', title=_('Open date'), data_type=datetime.date, visible=False), SearchColumn('deliver_date', title=_('Sent date'), data_type=datetime.date, visible=False), SearchColumn('receive_date', title=_('Received date'), data_type=datetime.date, visible=False), ]
def create_sellable_filter(self, label=None): from stoqlib.domain.sellable import Sellable items = [(desc, status) for status, desc in Sellable.statuses.items()] items.insert(0, (_(u"Any"), None)) if label is None: label = _('With status:') sellable_filter = ComboSearchFilter(label, items) # Select status available by default sellable_filter.select(Sellable.STATUS_AVAILABLE) return sellable_filter
class ProductionItemsSearch(SearchDialog): title = _(u'Production Items') search_table = ProductionItemView size = (750, 450) # # SearchDialog # def create_filters(self): self.set_text_field_columns(['description']) statuses = [(desc, i) for i, desc in ProductionOrder.statuses.items()] statuses.insert(0, (_(u'Any'), None)) self.status_filter = ComboSearchFilter(_('order status:'), statuses) self.status_filter.select(ProductionOrder.ORDER_PRODUCING) self.add_filter(self.status_filter, columns=['order_status'], position=SearchFilterPosition.TOP) def get_columns(self): return [ IdentifierColumn('order_identifier', title=_(u"Order #"), sorted=True), SearchColumn('category_description', title=_(u'Category'), data_type=str), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), SearchColumn('unit_description', title=_(u'Unit'), data_type=str), SearchColumn('quantity', title=_(u'To Produce'), data_type=Decimal), SearchColumn('produced', title=_(u'Produced'), data_type=Decimal), SearchColumn('lost', title=_(u'Lost'), data_type=Decimal, visible=False) ] # # Callbacks # def on_print_button_clicked(self, widget): print_report( ProductionItemReport, self.results, list(self.results), filters=self.search.get_search_filters(), )
def create_filters(self): self.set_text_field_columns(['description']) self.executer.add_query_callback(self._get_query) # Status items = [(v, k) for k, v in Till.statuses.items() if k != Till.STATUS_PENDING] items.insert(0, (_(u'Any'), None)) status_filter = ComboSearchFilter(_(u'Show entries of type'), items) status_filter.select(Till.STATUS_OPEN) self.add_filter(status_filter, position=SearchFilterPosition.TOP, columns=['status'])
class ProductionItemsSearch(ProductSearch): title = _(u'Production Items') search_spec = ProductionItemView report_class = ProductionItemReport csv_data = None has_print_price_button = False text_field_columns = [ ProductionItemView.description, ProductionItemView.order_identifier_str ] def __init__(self, store, hide_footer=True, hide_toolbar=True): ProductSearch.__init__(self, store, hide_footer=hide_footer, hide_toolbar=hide_toolbar) # # SearchDialog # def create_filters(self): statuses = [(desc, i) for i, desc in ProductionOrder.statuses.items()] statuses.insert(0, (_(u'Any'), None)) self.status_filter = ComboSearchFilter(_('order status:'), statuses) self.status_filter.select(ProductionOrder.ORDER_PRODUCING) self.add_filter(self.status_filter, columns=['order_status'], position=SearchFilterPosition.TOP) def get_columns(self): return [ IdentifierColumn('order_identifier', title=_(u"Order #"), sorted=True), SearchColumn('category_description', title=_(u'Category'), data_type=str), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), SearchColumn('unit_description', title=_(u'Unit'), data_type=str), SearchColumn('quantity', title=_(u'To Produce'), data_type=Decimal), SearchColumn('produced', title=_(u'Produced'), data_type=Decimal), SearchColumn('lost', title=_(u'Lost'), data_type=Decimal, visible=False) ]
def create_branch_filter(self, label=None): from stoqlib.domain.person import Branch branches = Branch.get_active_branches(self.store) items = [(b.get_description(), b.id) for b in branches] items.insert(0, (_("Any"), None)) if not label: label = _("Branch:") branch_filter = ComboSearchFilter(label, items) current = api.get_current_branch(self.store) if current: branch_filter.select(current.id) return branch_filter
def create_payment_filter(self, label=None): from stoqlib.domain.payment.method import PaymentMethod methods = PaymentMethod.get_active_methods(self.store) items = [(_('Any'), None)] for method in methods: if method.method_name == 'multiple': continue items.append((method.description, method)) if not label: label = _('Method:') payment_filter = ComboSearchFilter(label, items) payment_filter.select(None) return payment_filter
def create_branch_filter(self, label=None): from stoqlib.domain.person import Branch current = api.get_current_branch(self.store) if api.sysparam.get_bool('SYNCHRONIZED_MODE'): items = [(current.get_description(), current.id)] else: branches = Branch.get_active_branches(self.store) items = [(b.get_description(), b.id) for b in branches] items.insert(0, (_("Any"), None)) if not label: label = _('Branch:') branch_filter = ComboSearchFilter(label, items) if current: branch_filter.select(current.id) return branch_filter
def create_branch_filter(self, label=None): from stoqlib.domain.person import Branch branches = Branch.get_active_branches(self.store) items = [(b.person.name, b.id) for b in branches] # if not items: # raise ValueError('You should have at least one branch at ' # 'this point') items.insert(0, (_("Any"), None)) if not label: label = _('Branch:') branch_filter = ComboSearchFilter(label, items) current = api.get_current_branch(self.store) if current: branch_filter.select(current.id) return branch_filter
def create_filters(self): statuses = [(v, k) for k, v in Client.statuses.items()] statuses.insert(0, (_('Any'), None)) status_filter = ComboSearchFilter(_('Show clients with status'), statuses) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ['status']) if self._birth_date: birthday_filter = self.search.add_filter_by_attribute( 'birth_date', _('Birthday'), datetime.date, callback=self.birthday_search) # FIXME: The fifth position is a search by day. This is done # elsewhere too but we should not hardcode it. Try to # find a better solution in the future and fix everything birthday_filter.mode.select_item_by_position(5) birthday_filter.start_date.set_date(self._birth_date) self.search.refresh()
def create_filters(self): self.set_text_field_columns(['description', 'barcode', 'category_description']) self.search.set_query(self.executer_query) # Branch branch_filter = self.create_branch_filter(_('In branch:')) self.add_filter(branch_filter, columns=[]) self.branch_filter = branch_filter # Status statuses = [(desc, id) for id, desc in Sellable.statuses.items()] statuses.insert(0, (_('Any'), None)) status_filter = ComboSearchFilter(_('with status:'), statuses) status_filter.select(None) self.add_filter(status_filter, columns=['status'], position=SearchFilterPosition.TOP) self.status_filter = status_filter
class ProductionItemsSearch(SearchDialog): title = _(u'Production Items') search_table = ProductionItemView size = (750, 450) # # SearchDialog # def create_filters(self): self.set_text_field_columns(['description']) statuses = [(desc, i) for i, desc in ProductionOrder.statuses.items()] statuses.insert(0, (_(u'Any'), None)) self.status_filter = ComboSearchFilter(_('order status:'), statuses) self.status_filter.select(ProductionOrder.ORDER_PRODUCING) self.add_filter(self.status_filter, columns=['order_status'], position=SearchFilterPosition.TOP) def get_columns(self): return [IdentifierColumn('order_identifier', title=_(u"Order #"), sorted=True), SearchColumn('category_description', title=_(u'Category'), data_type=str), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), SearchColumn('unit_description', title=_(u'Unit'), data_type=str), SearchColumn('quantity', title=_(u'To Produce'), data_type=Decimal), SearchColumn('produced', title=_(u'Produced'), data_type=Decimal), SearchColumn('lost', title=_(u'Lost'), data_type=Decimal, visible=False)] # # Callbacks # def on_print_button_clicked(self, widget): print_report(ProductionItemReport, self.results, list(self.results), filters=self.search.get_search_filters(), )
def create_filters(self): self.set_text_field_columns( ['description', 'barcode', 'category_description']) self.search.set_query(self.executer_query) # Branch branch_filter = self.create_branch_filter(_('In branch:')) branch_filter.select(None) self.add_filter(branch_filter, columns=[]) self.branch_filter = branch_filter # Status statuses = [(desc, id) for id, desc in Sellable.statuses.items()] statuses.insert(0, (_('Any'), None)) status_filter = ComboSearchFilter(_('with status:'), statuses) status_filter.select(None) self.add_filter(status_filter, columns=['status'], position=SearchFilterPosition.TOP) self.status_filter = status_filter
class ProductionItemsSearch(ProductSearch): title = _(u'Production Items') search_spec = ProductionItemView report_class = ProductionItemReport csv_data = None has_print_price_button = False text_field_columns = [ProductionItemView.description, ProductionItemView.order_identifier_str] def __init__(self, store, hide_footer=True, hide_toolbar=True): ProductSearch.__init__(self, store, hide_footer=hide_footer, hide_toolbar=hide_toolbar) # # SearchDialog # def create_filters(self): statuses = [(desc, i) for i, desc in ProductionOrder.statuses.items()] statuses.insert(0, (_(u'Any'), None)) self.status_filter = ComboSearchFilter(_('order status:'), statuses) self.status_filter.select(ProductionOrder.ORDER_PRODUCING) self.add_filter(self.status_filter, columns=['order_status'], position=SearchFilterPosition.TOP) def get_columns(self): return [IdentifierColumn('order_identifier', title=_(u"Order #"), sorted=True), SearchColumn('category_description', title=_(u'Category'), data_type=str), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), SearchColumn('unit_description', title=_(u'Unit'), data_type=str), SearchColumn('quantity', title=_(u'To Produce'), data_type=Decimal), SearchColumn('produced', title=_(u'Produced'), data_type=Decimal), SearchColumn('lost', title=_(u'Lost'), data_type=Decimal, visible=False)]
def create_branch_filter(self, label=None, column=None): """Returns a new branch filter. :param label: The label to be used for the filter :param column: When provided, besides creating the filter, we will also add it to the interface, filtering by the informed column. """ items = api.get_branches_for_filter(self.store, use_id=True) if not label: label = _('Branch:') if column and not isinstance(column, list): column = [column] branch_filter = ComboSearchFilter(label, items) current = api.get_current_branch(self.store) branch_filter.select(current.id) if column: self.add_filter(branch_filter, columns=column, position=SearchFilterPosition.TOP) return branch_filter
class InventoryApp(ShellApp): # TODO: Change all widget.set_sensitive to self.set_sensitive([widget]) app_title = _('Inventory') gladefile = "inventory" search_spec = Inventory search_labels = _('Matching:') report_table = InventoryReport # # Application # def create_actions(self): group = get_accels('app.inventory') actions = [ # File ('NewInventory', None, _('Inventory...'), group.get('new_inventory'), _('Create a new inventory for product counting')), # Inventory ('Details', Gtk.STOCK_INFO, _('Details...'), group.get('inventory_details'), _('See details about this inventory')), ('CountingAction', Gtk.STOCK_INDEX, _('_Count...'), group.get('inventory_count'), _('Register the actual stock of products in the selected ' 'inventory')), ('AdjustAction', Gtk.STOCK_CONVERT, _('_Adjust...'), group.get('inventory_adjust'), _('Adjust the stock accordingly to the counting in the selected ' 'inventory')), ('Cancel', Gtk.STOCK_CANCEL, _('Cancel...'), group.get('inventory_cancel'), _('Cancel the selected inventory')), ('Export', Gtk.STOCK_SAVE, _('Export for external counting...'), None, _('Export the list of products for external counting')), ('PrintProductListing', Gtk.STOCK_PRINT, _('Print product listing...'), group.get('inventory_print'), _('Print the product listing for this inventory')) ] self.inventory_ui = self.add_ui_actions(actions) self.set_help_section(_("Inventory help"), 'app-inventory') def create_ui(self): self.window.add_new_items([self.NewInventory]) self.window.add_print_items([self.PrintProductListing]) self.window.add_export_items([self.Export]) def activate(self, refresh=True): if refresh: # Avoid letting this sensitive if has-rows is never emitted self.refresh() self._update_widgets() self.search.focus_search_entry() def create_filters(self): # Disable string search right now, until we use a proper Viewable # for this application self.search.disable_search_entry() self.branch_filter = ComboSearchFilter(_('Show inventories at:'), self._get_branches_for_filter()) current = api.get_current_branch(self.store) self.branch_filter.select(current.id) self.add_filter(self.branch_filter, SearchFilterPosition.TOP, columns=["branch_id"]) def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'inventory.Details', True), ('fa-list-ol-symbolic', _('Count'), 'inventory.CountingAction', True), ('fa-tasks-symbolic', _('Adjust'), 'inventory.AdjustAction', True), ('fa-ban-symbolic', _('Cancel'), 'inventory.Cancel', True), ] return options def get_columns(self): return [ IdentifierColumn('identifier', title=_('Inventory #'), sorted=True, order=Gtk.SortType.DESCENDING), SearchColumn('status_str', title=_('Status'), data_type=str, width=100, valid_values=self._get_status_values(), search_attribute='status'), Column('branch.description', title=_('Branch'), data_type=str, expand=True), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), data_type=datetime.date, width=120), SearchColumn('close_date', title=_('Closed'), long_title=_('Date Closed'), data_type=datetime.date, width=120) ] # # Private API # def _get_status_values(self): values = [(v, k) for k, v in Inventory.statuses.items()] values.insert(0, (_("Any"), None)) return values def _get_branches(self): return self.store.find(Branch) def _get_branches_for_filter(self): items = [(b.get_description(), b.id) for b in self._get_branches()] if not items: raise DatabaseInconsistency('You should have at least one ' 'branch on your database.' 'Found zero') items.insert(0, [_('All branches'), None]) return items def _update_widgets(self): has_open = False all_counted = False has_adjusted = False selected = self.results.get_selected() if selected: all_counted = selected.all_items_counted() has_open = selected.is_open() has_adjusted = selected.has_adjusted_items() self.set_sensitive([self.PrintProductListing, self.Details], bool(selected)) self.set_sensitive([self.Cancel], has_open and not has_adjusted) self.set_sensitive([self.NewInventory], self._can_open()) self.set_sensitive([self.CountingAction], has_open and not all_counted) self.set_sensitive([self.AdjustAction], has_open and all_counted) def _can_open(self): branch = api.get_current_branch(self.store) if Inventory.has_open(self.store, branch): return False # It doesn't make sense to open an inventory if we don't have any stock return self.store.find(ProductStockItem, branch=branch).count() > 0 def _open_inventory(self): with api.new_store() as store: rv = self.run_dialog(InventoryOpenEditor, store) if rv: self.refresh() self._update_widgets() def _cancel_inventory(self): if not yesno(_('Are you sure you want to cancel this inventory ?'), Gtk.ResponseType.NO, _("Cancel inventory"), _("Don't cancel")): return store = api.new_store() inventory = store.fetch(self.results.get_selected()) inventory.cancel() store.commit() store.close() self.refresh() self._update_widgets() def _export_product_list(self): filename = save(_("Save products file"), self.get_toplevel(), "%s.txt" % (_('products'))) if not filename: return tables = [ Sellable, Join(Storable, Storable.id == Sellable.id), ] sellables = self.store.using(*tables).find(Sellable) with open(filename, 'w') as fh: for sellable in sellables: # TODO: Add a dialog for the user to choose the format for # exporting fh.write('%-20s%s\n' % (sellable.barcode, sellable.description)) def _register_product_counting(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) model = self.run_dialog(InventoryCountWizard, store, inventory) store.confirm(model) store.close() self.refresh() self._update_widgets() def _adjust_product_quantities(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) model = self.run_dialog(InventoryAdjustmentEditor, store, inventory) store.confirm(model) store.close() self.refresh() self._update_widgets() def _update_filter_slave(self, slave): self.refresh() def _get_sellables_by_inventory(self, inventory): for item in inventory.get_items(): yield item.product.sellable def _show_inventory_details(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) self.run_dialog(InventoryDetailsDialog, store, inventory) store.close() # # Callbacks # def on_NewInventory__activate(self, action): self._open_inventory() def on_CountingAction__activate(self, action): self._register_product_counting() def on_AdjustAction__activate(self, action): self._adjust_product_quantities() def on_Details__activate(self, action): self._show_inventory_details() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__double_click(self, results, inventory): self._show_inventory_details() def on_Cancel__activate(self, widget): self._cancel_inventory() def on_Export__activate(self, widget): self._export_product_list() def on_PrintProductListing__activate(self, button): selected = self.results.get_selected() sellables = list(self._get_sellables_by_inventory(selected)) if not sellables: warning(_("No products found in the inventory.")) return self.print_report(ProductCountingReport, sellables)
class TransferOrderSearch(SearchDialog): title = _(u"Transfer Order Search") size = (750, 500) search_spec = TransferOrderView selection_mode = gtk.SELECTION_MULTIPLE search_by_date = True advanced_search = False def __init__(self, store): SearchDialog.__init__(self, store) self._setup_widgets() def _show_transfer_order_details(self, order_view): transfer_order = order_view.transfer_order run_dialog(TransferOrderDetailsDialog, self, self.store, transfer_order) def _setup_widgets(self): self.results.connect('row_activated', self.on_row_activated) self.update_widgets() def _get_status_values(self): items = [(str(value), key) for key, value in TransferOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items # # SearchDialog Hooks # def update_widgets(self): orders = self.results.get_selected_rows() has_one_selected = len(orders) == 1 self.set_details_button_sensitive(has_one_selected) self.set_print_button_sensitive(has_one_selected) def _has_rows(self, results, obj): pass def create_filters(self): self.set_text_field_columns([ 'source_branch_name', 'destination_branch_name', 'identifier_str' ]) # Date self.date_filter = DateSearchFilter(_('Date:')) self.add_filter(self.date_filter, columns=['open_date', 'receival_date']) # Branch self.branch_filter = self.create_branch_filter(_('To branch:')) self.add_filter(self.branch_filter, columns=['destination_branch_id']) # Status statuses = self._get_status_values() self.status_filter = ComboSearchFilter(_('With status:'), statuses) self.status_filter.select(None) self.add_filter(self.status_filter, columns=['status'], position=SearchFilterPosition.TOP) def get_columns(self): return [ IdentifierColumn('identifier'), SearchColumn('transfer_order.status_str', _('Status'), data_type=str, valid_values=self._get_status_values(), search_attribute='status', width=100), SearchColumn('open_date', _('Open date'), data_type=datetime.date, sorted=True, width=100), SearchColumn('receival_date', _('Receival Date'), data_type=datetime.date, width=100, visible=False), SearchColumn('source_branch_name', _('Source'), data_type=unicode, expand=True), SearchColumn('destination_branch_name', _('Destination'), data_type=unicode, width=220), Column('total_items', _('Items'), data_type=Decimal, format_func=format_quantity, width=110) ] # # Callbacks # def on_row_activated(self, klist, view): self._show_transfer_order_details(view) def on_print_button_clicked(self, button): view = self.results.get_selected_rows()[0] print_report(TransferOrderReceipt, view.transfer_order) def on_details_button_clicked(self, button): self._show_transfer_order_details(self.results.get_selected_rows()[0])
class TransferOrderSearch(SearchDialog): title = _(u"Transfer Order Search") size = (750, 500) search_spec = TransferOrderView report_class = TransferOrderReport selection_mode = gtk.SELECTION_MULTIPLE def __init__(self, store): SearchDialog.__init__(self, store) self._setup_widgets() def _show_transfer_order_details(self, order_view): transfer_order = order_view.transfer_order with api.new_store() as store: model = store.fetch(transfer_order) run_dialog(TransferOrderDetailsDialog, self, store, model) store.retval = store.get_pending_count() > 0 def _setup_widgets(self): self.results.connect('row_activated', self.on_row_activated) self.update_widgets() def _get_status_values(self): items = [(str(value), key) for key, value in TransferOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items # # SearchDialog Hooks # def update_widgets(self): orders = self.results.get_selected_rows() has_one_selected = len(orders) == 1 self.set_details_button_sensitive(has_one_selected) self.set_print_button_sensitive(has_one_selected) def create_filters(self): self.set_text_field_columns(['source_branch_name', 'destination_branch_name', 'identifier_str']) # Date self.date_filter = DateSearchFilter(_('Date:')) self.add_filter(self.date_filter, columns=['open_date', 'finish_date']) # Status self.status_filter = ComboSearchFilter(_('With status:'), self._get_status_options()) self.status_filter.select('pending') executer = self.search.get_query_executer() executer.add_filter_query_callback(self.status_filter, self._get_status_query) self.add_filter(self.status_filter, position=SearchFilterPosition.TOP) def _get_status_options(self): return [ (_('All transfers'), None), (_('Pending receive'), 'pending'), (_('Received'), 'received'), (_('Sent'), 'sent'), (_('Cancelled'), 'cancelled'), ] def _get_status_query(self, state): current_branch = api.get_current_branch(self.store) if state.value == 'pending': return And(TransferOrder.status == TransferOrder.STATUS_SENT, TransferOrder.destination_branch_id == current_branch.id) elif state.value == 'received': return And(TransferOrder.status == TransferOrder.STATUS_RECEIVED, TransferOrder.destination_branch_id == current_branch.id) elif state.value == 'sent': return And(TransferOrder.source_branch_id == current_branch.id, Not(TransferOrder.status == TransferOrder.STATUS_CANCELLED)) elif state.value == 'cancelled': return And(TransferOrder.status == TransferOrder.STATUS_CANCELLED, TransferOrder.source_branch_id == current_branch.id) else: return Or(TransferOrder.source_branch_id == current_branch.id, TransferOrder.destination_branch_id == current_branch.id) def get_columns(self): return [IdentifierColumn('identifier', title=_('Transfer #')), SearchColumn('transfer_order.status_str', _('Status'), data_type=str, valid_values=self._get_status_values(), search_attribute='status', width=100), SearchColumn('open_date', _('Open date'), data_type=datetime.date, sorted=True, width=100), SearchColumn('finish_date', _('Finish Date'), data_type=datetime.date, width=100, visible=False), SearchColumn('source_branch_name', _('Source'), data_type=str, expand=True), SearchColumn('destination_branch_name', _('Destination'), data_type=str, width=220), Column('total_items', _('Items'), data_type=Decimal, format_func=format_quantity, width=110)] # # Callbacks # def on_row_activated(self, klist, view): self._show_transfer_order_details(view) def on_details_button_clicked(self, button): self._show_transfer_order_details(self.results.get_selected_rows()[0])
class InventoryApp(ShellApp): # TODO: Change all widget.set_sensitive to self.set_sensitive([widget]) app_title = _('Inventory') gladefile = "inventory" search_spec = Inventory search_labels = _('Matching:') report_table = InventoryReport # # Application # def create_actions(self): group = get_accels('app.inventory') actions = [ # File ('NewInventory', None, _('Inventory...'), group.get('new_inventory'), _('Create a new inventory for product counting')), # Inventory ('Details', Gtk.STOCK_INFO, _('Details...'), group.get('inventory_details'), _('See details about this inventory')), ('CountingAction', Gtk.STOCK_INDEX, _('_Count...'), group.get('inventory_count'), _('Register the actual stock of products in the selected ' 'inventory')), ('AdjustAction', Gtk.STOCK_CONVERT, _('_Adjust...'), group.get('inventory_adjust'), _('Adjust the stock accordingly to the counting in the selected ' 'inventory')), ('Cancel', Gtk.STOCK_CANCEL, _('Cancel...'), group.get('inventory_cancel'), _('Cancel the selected inventory')), ('Export', Gtk.STOCK_SAVE, _('Export for external counting...'), None, _('Export the list of products for external counting')), ('PrintProductListing', Gtk.STOCK_PRINT, _('Print product listing...'), group.get('inventory_print'), _('Print the product listing for this inventory')) ] self.inventory_ui = self.add_ui_actions(actions) self.set_help_section(_("Inventory help"), 'app-inventory') def create_ui(self): self.window.add_new_items([self.NewInventory]) self.window.add_print_items([self.PrintProductListing]) self.window.add_export_items([self.Export]) def activate(self, refresh=True): if refresh: # Avoid letting this sensitive if has-rows is never emitted self.refresh() self._update_widgets() self.search.focus_search_entry() def create_filters(self): # Disable string search right now, until we use a proper Viewable # for this application self.search.disable_search_entry() self.branch_filter = ComboSearchFilter(_('Show inventories at:'), self._get_branches_for_filter()) current = api.get_current_branch(self.store) self.branch_filter.select(current.id) self.add_filter(self.branch_filter, SearchFilterPosition.TOP, columns=["branch_id"]) def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'inventory.Details', True), ('fa-list-ol-symbolic', _('Count'), 'inventory.CountingAction', True), ('fa-tasks-symbolic', _('Adjust'), 'inventory.AdjustAction', True), ('fa-ban-symbolic', _('Cancel'), 'inventory.Cancel', True), ] return options def get_columns(self): return [IdentifierColumn('identifier', title=_('Inventory #'), sorted=True, order=Gtk.SortType.DESCENDING), SearchColumn('status_str', title=_('Status'), data_type=str, width=100, valid_values=self._get_status_values(), search_attribute='status'), Column('branch.description', title=_('Branch'), data_type=str, expand=True), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), data_type=datetime.date, width=120), SearchColumn('close_date', title=_('Closed'), long_title=_('Date Closed'), data_type=datetime.date, width=120)] # # Private API # def _get_status_values(self): values = [(v, k) for k, v in Inventory.statuses.items()] values.insert(0, (_("Any"), None)) return values def _get_branches(self): return self.store.find(Branch) def _get_branches_for_filter(self): items = [(b.get_description(), b.id) for b in self._get_branches()] if not items: raise DatabaseInconsistency('You should have at least one ' 'branch on your database.' 'Found zero') items.insert(0, [_('All branches'), None]) return items def _update_widgets(self): has_open = False all_counted = False has_adjusted = False selected = self.results.get_selected() if selected: all_counted = selected.all_items_counted() has_open = selected.is_open() has_adjusted = selected.has_adjusted_items() self.set_sensitive([self.PrintProductListing, self.Details], bool(selected)) self.set_sensitive([self.Cancel], has_open and not has_adjusted) self.set_sensitive([self.NewInventory], self._can_open()) self.set_sensitive([self.CountingAction], has_open and not all_counted) self.set_sensitive([self.AdjustAction], has_open and all_counted) def _can_open(self): branch = api.get_current_branch(self.store) if Inventory.has_open(self.store, branch): return False # It doesn't make sense to open an inventory if we don't have any stock return self.store.find(ProductStockItem, branch=branch).count() > 0 def _open_inventory(self): with api.new_store() as store: rv = self.run_dialog(InventoryOpenEditor, store) if rv: self.refresh() self._update_widgets() def _cancel_inventory(self): if not yesno(_('Are you sure you want to cancel this inventory ?'), Gtk.ResponseType.NO, _("Cancel inventory"), _("Don't cancel")): return store = api.new_store() inventory = store.fetch(self.results.get_selected()) inventory.cancel() store.commit() store.close() self.refresh() self._update_widgets() def _export_product_list(self): filename = save(_("Save products file"), self.get_toplevel(), "%s.txt" % (_('products'))) if not filename: return tables = [ Sellable, Join(Storable, Storable.id == Sellable.id), ] sellables = self.store.using(*tables).find(Sellable) with open(filename, 'w') as fh: for sellable in sellables: # TODO: Add a dialog for the user to choose the format for # exporting fh.write('%-20s%s\n' % (sellable.barcode, sellable.description)) def _register_product_counting(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) model = self.run_dialog(InventoryCountWizard, store, inventory) store.confirm(model) store.close() self.refresh() self._update_widgets() def _adjust_product_quantities(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) model = self.run_dialog(InventoryAdjustmentEditor, store, inventory) store.confirm(model) store.close() self.refresh() self._update_widgets() def _update_filter_slave(self, slave): self.refresh() def _get_sellables_by_inventory(self, inventory): for item in inventory.get_items(): yield item.product.sellable def _show_inventory_details(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) self.run_dialog(InventoryDetailsDialog, store, inventory) store.close() # # Callbacks # def on_NewInventory__activate(self, action): self._open_inventory() def on_CountingAction__activate(self, action): self._register_product_counting() def on_AdjustAction__activate(self, action): self._adjust_product_quantities() def on_Details__activate(self, action): self._show_inventory_details() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__double_click(self, results, inventory): self._show_inventory_details() def on_Cancel__activate(self, widget): self._cancel_inventory() def on_Export__activate(self, widget): self._export_product_list() def on_PrintProductListing__activate(self, button): selected = self.results.get_selected() sellables = list(self._get_sellables_by_inventory(selected)) if not sellables: warning(_("No products found in the inventory.")) return self.print_report(ProductCountingReport, sellables)
class StockApp(ShellApp): app_title = _('Stock') gladefile = "stock" search_spec = ProductFullStockView search_labels = _('Matching:') report_table = SimpleProductReport pixbuf_converter = converter.get_converter(GdkPixbuf.Pixbuf) # # Application # def create_actions(self): group = get_accels('app.stock') actions = [ ("NewReceiving", STOQ_RECEIVING, _("Order _receival..."), group.get('new_receiving')), ('NewTransfer', Gtk.STOCK_CONVERT, _('Transfer...'), group.get('transfer_product')), ('NewStockDecrease', None, _('Stock decrease...'), group.get('stock_decrease')), ('StockInitial', Gtk.STOCK_GO_UP, _('Register initial stock...')), ("LoanNew", None, _("Loan...")), ("LoanClose", None, _("Close loan...")), ("SearchPurchaseReceiving", None, _("Received purchases..."), group.get('search_receiving'), _("Search for received purchase orders")), ("SearchProductHistory", None, _("Product history..."), group.get('search_product_history'), _("Search for product history")), ("SearchStockDecrease", None, _("Stock decreases..."), '', _("Search for manual stock decreases")), ("SearchPurchasedStockItems", None, _("Purchased items..."), group.get('search_purchased_stock_items'), _("Search for purchased items")), ("SearchBrandItems", None, _("Brand items..."), group.get('search_brand_items'), _("Search for brand items on stock")), ("SearchBrandItemsByBranch", None, _("Brand item by branch..."), group.get('search_brand_by_branch'), _("Search for brand items by branch on stock")), ("SearchBatchItems", None, _("Batch items..."), group.get('search_batch_items'), _("Search for batch items on stock")), ("SearchStockItems", None, _("Stock items..."), group.get('search_stock_items'), _("Search for items on stock")), ("SearchTransfer", None, _("Transfers..."), group.get('search_transfers'), _("Search for stock transfers")), ("SearchClosedStockItems", None, _("Closed stock Items..."), group.get('search_closed_stock_items'), _("Search for closed stock items")), ("LoanSearch", None, _("Loans...")), ("LoanSearchItems", None, _("Loan items...")), ("SearchTransferItems", None, _("Transfer items...")), ("SearchReturnedItems", None, _("Returned items...")), ("SearchPendingReturnedSales", None, _("Pending returned sales...")), ("ProductMenu", None, _("Product")), ("PrintLabels", None, _("Print labels...")), ("ManageStock", None, _("Manage stock...")), ("ProductStockHistory", Gtk.STOCK_INFO, _("History..."), group.get('history'), _('Show the stock history of the selected product')), ("EditProduct", Gtk.STOCK_EDIT, _("Edit..."), group.get('edit_product'), _("Edit the selected product, allowing you to change it's " "details")), ] self.stock_ui = self.add_ui_actions('', actions, filename='stock.xml') toggle_actions = [ ('StockPictureViewer', None, _('Picture viewer'), group.get('toggle_picture_viewer')), ] self.add_ui_actions('', toggle_actions, 'ToggleActions', 'toggle') self.set_help_section(_("Stock help"), 'app-stock') self.NewReceiving.set_short_label(_("Receive")) self.NewTransfer.set_short_label(_("Transfer")) self.EditProduct.set_short_label(_("Edit")) self.ProductStockHistory.set_short_label(_("History")) self.EditProduct.props.is_important = True self.ProductStockHistory.props.is_important = True def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.popup = self.uimanager.get_widget('/StockSelection') self.window.add_new_items([self.NewReceiving, self.NewTransfer, self.NewStockDecrease, self.LoanNew]) self.window.add_search_items([ self.SearchStockItems, self.SearchBrandItems, self.SearchStockDecrease, self.SearchClosedStockItems, self.SearchProductHistory, self.SearchPurchasedStockItems, self.SearchTransfer, ]) self.window.Print.set_tooltip( _("Print a report of these products")) self._inventory_widgets = [self.NewTransfer, self.NewReceiving, self.StockInitial, self.NewStockDecrease, self.LoanNew, self.LoanClose] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.image_viewer = None self.image = Gtk.Image() self.edit_button = self.uimanager.get_widget('/toolbar/AppToolbarPH/EditProduct') self.edit_button.set_icon_widget(self.image) self.image.show() self.search.set_summary_label(column='stock', label=_('<b>Stock Total:</b>'), format='<b>%s</b>', parent=self.get_statusbar_message_area()) def activate(self, refresh=True): self.window.NewToolItem.set_tooltip( _("Create a new receiving order")) self.window.SearchToolItem.set_tooltip( _("Search for stock items")) if refresh: self.refresh() open_inventory = self.check_open_inventory() if not open_inventory: self.transfers_bar = self._create_pending_info_message() self.returned_bar = self._create_pending_returned_sale_message() else: self.transfers_bar = None self.returned_bar = None self._update_widgets() self.search.focus_search_entry() def deactivate(self): if self.transfers_bar: self.transfers_bar.hide() if self.returned_bar: self.returned_bar.hide() self.uimanager.remove_ui(self.stock_ui) self._close_image_viewer() def new_activate(self): if not self.NewReceiving.get_sensitive(): warning(_("You cannot receive a purchase with an open inventory.")) return self._receive_purchase() def search_activate(self): self.run_dialog(ProductStockSearch, self.store) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.search.set_query(self._query) self.set_text_field_columns(['description', 'code', 'barcode', 'category_description', 'manufacturer']) branches = Branch.get_active_branches(self.store) self.branch_filter = ComboSearchFilter( _('Show by:'), api.for_combo(branches, empty=_("All branches"))) self.branch_filter.select(api.get_current_branch(self.store)) self.add_filter(self.branch_filter, position=SearchFilterPosition.TOP) def get_columns(self): return [SearchColumn('code', title=_('Code'), sorted=True, sort_func=sort_sellable_code, data_type=str, width=130), SearchColumn('barcode', title=_("Barcode"), data_type=str, width=130), SearchColumn('category_description', title=_("Category"), data_type=str, width=100, visible=False), SearchColumn('description', title=_("Description"), data_type=str, expand=True, ellipsize=Pango.EllipsizeMode.END), SearchColumn('manufacturer', title=_("Manufacturer"), data_type=str, visible=False), SearchColumn('brand', title=_("Brand"), data_type=str, visible=False), SearchColumn('model', title=_("Model"), data_type=str, visible=False), SearchColumn('location', title=_("Location"), data_type=str, width=100, visible=False), QuantityColumn('stock', title=_('Quantity'), width=100, use_having=True), SearchColumn('has_image', title=_('Picture'), data_type=bool, width=80), ] # # Private API # def _open_image_viewer(self): assert self.image_viewer is None self.image_viewer = SellableImageViewer(size=(325, 325)) self.image_viewer.toplevel.connect( 'delete-event', self.on_image_viewer_closed) self.image_viewer.show_all() self._update_widgets() def _close_image_viewer(self): if self.image_viewer is None: return self.image_viewer.destroy() self.image_viewer = None def _query(self, store): branch = self.branch_filter.get_state().value return self.search_spec.find_by_branch(store, branch) def _update_widgets(self): branch = api.get_current_branch(self.store) is_main_branch = self.branch_filter.get_state().value is branch item = self.results.get_selected() sellable = item and item.product.sellable if sellable: if item.has_image: # XXX:Workaround for a bug caused by the image domain refactoring # which left some existent thumbnail as None thumbnail = sellable.image.thumbnail if thumbnail is None: # Create new store to create the thumbnail with api.new_store() as new_store: image = sellable.image image = new_store.fetch(image) size = (Image.THUMBNAIL_SIZE_WIDTH, Image.THUMBNAIL_SIZE_HEIGHT) image.thumbnail = get_thumbnail(image.image, size) thumbnail = image.thumbnail pixbuf = self.pixbuf_converter.from_string(thumbnail) else: pixbuf = None self._update_edit_image(pixbuf) if self.image_viewer: self.image_viewer.set_sellable(sellable) else: self._update_edit_image() # Always let the user choose the manage stock option and do a proper # check there (showing a warning if he can't) self.set_sensitive([self.ManageStock], bool(item)) self.set_sensitive([self.EditProduct, self.PrintLabels], bool(item)) self.set_sensitive([self.ProductStockHistory], bool(item) and is_main_branch) # We need more than one branch to be able to do transfers # Note that 'all branches' is not a real branch has_branches = len(self.branch_filter.combo) > 2 transfer_active = self.NewTransfer.get_sensitive() self.set_sensitive([self.NewTransfer], transfer_active and has_branches) # Building a list of searches that we must disable if there is no # branches other than the main company searches = [self.SearchTransfer, self.SearchTransferItems, self.SearchPendingReturnedSales] self.set_sensitive(searches, has_branches) def _update_edit_image(self, pixbuf=None): if not pixbuf: self.image.set_from_stock(Gtk.STOCK_EDIT, Gtk.IconSize.LARGE_TOOLBAR) return # FIXME: get this icon size from settings icon_size = 24 pixbuf = pixbuf.scale_simple(icon_size, icon_size, GdkPixbuf.InterpType.BILINEAR) self.image.set_from_pixbuf(pixbuf) def _update_filter_slave(self, slave): self.refresh() def _transfer_stock(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockTransferWizard, store) store.confirm(model) store.close() self.refresh() def _receive_purchase(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(ReceivingOrderWizard, store) store.confirm(model) store.close() self.refresh() def _create_pending_info_message(self): branch = api.get_current_branch(self.store) n_transfers = TransferOrder.get_pending_transfers(self.store, branch).count() if not n_transfers: return None msg = stoqlib_ngettext(_(u"You have %s incoming transfer"), _(u"You have %s incoming transfers"), n_transfers) % n_transfers info_bar = self.window.add_info_bar(Gtk.MessageType.QUESTION, msg) button = info_bar.add_button(_(u"Receive"), Gtk.ResponseType.OK) button.connect('clicked', self._on_info_transfers__clicked) return info_bar def _create_pending_returned_sale_message(self): branch = api.get_current_branch(self.store) n_returned = ReturnedSale.get_pending_returned_sales(self.store, branch).count() if not n_returned: return None msg = stoqlib_ngettext(_(u"You have %s returned sale to receive"), _(u"You have %s returned sales to receive"), n_returned) % n_returned info_returned_bar = self.window.add_info_bar(Gtk.MessageType.QUESTION, msg) button = info_returned_bar.add_button(_(u"Returned sale"), Gtk.ResponseType.OK) button.connect('clicked', self._on_info_returned_sales__clicked) return info_returned_bar def _search_transfers(self): branch = api.get_current_branch(self.store) self.run_dialog(TransferOrderSearch, self.store) # After the search is closed we may want to update , or even hide the # message, if there is no pending transfer to receive if self.transfers_bar: n_transfers = TransferOrder.get_pending_transfers(self.store, branch).count() if n_transfers > 0: msg = stoqlib_ngettext(_(u"You have %s incoming transfer"), _(u"You have %s incoming transfers"), n_transfers) % n_transfers self.transfers_bar.set_message(msg) else: self.transfers_bar.hide() self.refresh() def _search_pending_returned_sales(self): with api.new_store() as store: self.run_dialog(PendingReturnedSaleSearch, store) branch = api.get_current_branch(self.store) # After the search is closed we may want to update , or even hide the # message, if there is no pending returned sale to receive if self.returned_bar: n_returned = ReturnedSale.get_pending_returned_sales(self.store, branch).count() if n_returned > 0: msg = stoqlib_ngettext(_(u"You have %s returned sale to receive"), _(u"You have %s returned sales to receive"), n_returned) % n_returned self.returned_bar.set_message(msg) else: self.returned_bar.hide() self.refresh() # # Callbacks # def on_image_viewer_closed(self, window, event): self.image_viewer = None self.StockPictureViewer.set_active(False) def on_results__has_rows(self, results, product): self._update_widgets() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, None, event.button.button, event.time) def on_ProductStockHistory__activate(self, button): selected = self.results.get_selected() sellable = selected.sellable self.run_dialog(ProductStockHistoryDialog, self.store, sellable, branch=self.branch_filter.combo.get_selected()) def on_ManageStock__activate(self, action): user = api.get_current_user(self.store) if not user.profile.check_app_permission(u'inventory'): return warning(_('Only users with access to the inventory app can' ' change the stock quantity')) product = self.results.get_selected().product if product.is_grid: return warning(_("Can't change stock quantity of a grid parent")) if product.storable and product.storable.is_batch: return warning(_("It's not possible to change the stock quantity of" " a batch product")) branch = self.branch_filter.combo.get_selected() if not branch: return warning(_('You must select a branch first')) with api.new_store() as store: self.run_dialog(ProductStockQuantityEditor, store, store.fetch(product), branch=branch) if store.committed: self.refresh() def on_PrintLabels__activate(self, button): selected = self.results.get_selected() sellable = selected.sellable label_data = self.run_dialog(PrintLabelEditor, None, self.store, sellable) if label_data: print_labels(label_data, self.store) def on_EditProduct__activate(self, button): selected = self.results.get_selected() assert selected store = api.new_store() product = store.fetch(selected.product) model = self.run_dialog(ProductStockEditor, store, product) store.confirm(model) store.close() if model: self.refresh() def _on_info_transfers__clicked(self, button): self._search_transfers() def _on_info_returned_sales__clicked(self, button): self._search_pending_returned_sales() # Stock def on_NewReceiving__activate(self, button): self._receive_purchase() def on_NewTransfer__activate(self, button): self._transfer_stock() def on_NewStockDecrease__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockDecreaseWizard, store) store.confirm(model) store.close() self.refresh() def on_StockInitial__activate(self, action): if self.check_open_inventory(): return with api.new_store() as store: self.run_dialog(InitialStockDialog, store) if store.committed: self.refresh() def on_StockPictureViewer__toggled(self, button): if button.get_active(): self._open_image_viewer() else: self._close_image_viewer() # Loan def on_LoanNew__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(NewLoanWizard, store) store.confirm(model) store.close() self.refresh() def on_LoanClose__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(CloseLoanWizard, store) store.confirm(model) store.close() self.refresh() def on_LoanSearch__activate(self, action): self.run_dialog(LoanSearch, self.store) def on_LoanSearchItems__activate(self, action): self.run_dialog(LoanItemSearch, self.store) # Search def on_SearchPurchaseReceiving__activate(self, button): self.run_dialog(PurchaseReceivingSearch, self.store) def on_SearchTransfer__activate(self, action): self._search_transfers() def on_SearchTransferItems__activate(self, action): self.run_dialog(TransferItemSearch, self.store) def on_SearchPendingReturnedSales__activate(self, action): self._search_pending_returned_sales() def on_SearchReturnedItems__activate(self, action): self.run_dialog(ReturnedItemSearch, self.store) def on_SearchPurchasedStockItems__activate(self, action): self.run_dialog(PurchasedItemsSearch, self.store) def on_SearchStockItems__activate(self, action): self.run_dialog(ProductStockSearch, self.store) def on_SearchBrandItems__activate(self, action): self.run_dialog(ProductBrandSearch, self.store) def on_SearchBrandItemsByBranch__activate(self, action): self.run_dialog(ProductBrandByBranchSearch, self.store) def on_SearchBatchItems__activate(self, action): self.run_dialog(ProductBatchSearch, self.store) def on_SearchClosedStockItems__activate(self, action): self.run_dialog(ProductClosedStockSearch, self.store) def on_SearchProductHistory__activate(self, action): self.run_dialog(ProductSearchQuantity, self.store) def on_SearchStockDecrease__activate(self, action): self.run_dialog(StockDecreaseSearch, self.store)
class StockApp(ShellApp): app_title = _('Stock') gladefile = "stock" search_table = ProductFullStockView search_labels = _('Matching:') report_table = SimpleProductReport pixbuf_converter = converter.get_converter(gtk.gdk.Pixbuf) # # Application # def create_actions(self): group = get_accels('app.stock') actions = [ ("NewReceiving", STOQ_RECEIVING, _("Order _receival..."), group.get('new_receiving')), ('NewTransfer', gtk.STOCK_CONVERT, _('Transfer...'), group.get('transfer_product')), ('NewStockDecrease', None, _('Stock decrease...'), group.get('stock_decrease')), ('StockInitial', gtk.STOCK_GO_UP, _('Register initial stock...')), ("LoanNew", None, _("Loan...")), ("LoanClose", None, _("Close loan...")), ("SearchPurchaseReceiving", None, _("Received purchases..."), group.get('search_receiving'), _("Search for received purchase orders")), ("SearchProductHistory", None, _("Product history..."), group.get('search_product_history'), _("Search for product history")), ("SearchStockDecrease", None, _("Stock decreases..."), '', _("Search for manual stock decreases")), ("SearchPurchasedStockItems", None, _("Purchased items..."), group.get('search_purchased_stock_items'), _("Search for purchased items")), ("SearchStockItems", None, _("Stock items..."), group.get('search_stock_items'), _("Search for items on stock")), ("SearchTransfer", None, _("Transfers..."), group.get('search_transfers'), _("Search for stock transfers")), ("SearchClosedStockItems", None, _("Closed stock Items..."), group.get('search_closed_stock_items'), _("Search for closed stock items")), ("LoanSearch", None, _("Loans...")), ("LoanSearchItems", None, _("Loan items...")), ("ProductMenu", None, _("Product")), ("ProductStockHistory", gtk.STOCK_INFO, _("History..."), group.get('history'), _('Show the stock history of the selected product')), ("EditProduct", gtk.STOCK_EDIT, _("Edit..."), group.get('edit_product'), _("Edit the selected product, allowing you to change it's " "details")), ] self.stock_ui = self.add_ui_actions('', actions, filename='stock.xml') toggle_actions = [ ('StockPictureViewer', None, _('Picture viewer'), group.get('toggle_picture_viewer')), ] self.add_ui_actions('', toggle_actions, 'ToggleActions', 'toggle') self.set_help_section(_("Stock help"), 'app-stock') self.NewReceiving.set_short_label(_("Receive")) self.NewTransfer.set_short_label(_("Transfer")) self.EditProduct.set_short_label(_("Edit")) self.ProductStockHistory.set_short_label(_("History")) self.EditProduct.props.is_important = True self.ProductStockHistory.props.is_important = True def create_ui(self): if api.sysparam(self.store).SMART_LIST_LOADING: self.search.enable_lazy_search() self.popup = self.uimanager.get_widget('/StockSelection') self.window.add_new_items([self.NewReceiving, self.NewTransfer, self.NewStockDecrease, self.LoanNew]) self.window.add_search_items([ self.SearchStockItems, self.SearchStockDecrease, self.SearchClosedStockItems, self.SearchProductHistory, self.SearchPurchasedStockItems, self.SearchTransfer, ]) self.window.Print.set_tooltip( _("Print a report of these products")) self._inventory_widgets = [self.NewTransfer, self.NewReceiving, self.StockInitial, self.NewStockDecrease, self.LoanNew, self.LoanClose] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.image_viewer = None self.image = gtk.Image() self.edit_button = self.uimanager.get_widget('/toolbar/AppToolbarPH/EditProduct') self.edit_button.set_icon_widget(self.image) self.image.show() self.search.set_summary_label(column='stock', label=_('<b>Stock Total:</b>'), format='<b>%s</b>', parent=self.get_statusbar_message_area()) def activate(self, params): self.window.NewToolItem.set_tooltip( _("Create a new receiving order")) self.window.SearchToolItem.set_tooltip( _("Search for stock items")) self.check_open_inventory() self._update_widgets() def setup_focus(self): self.search.refresh() def deactivate(self): self.uimanager.remove_ui(self.stock_ui) def new_activate(self): if not self.NewReceiving.get_sensitive(): warning(_("You cannot receive a purchase with an open inventory.")) return self._receive_purchase() def search_activate(self): self.run_dialog(ProductStockSearch, self.store) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.executer.set_query(self._query) self.set_text_field_columns(['description']) self.branch_filter = ComboSearchFilter( _('Show by:'), self._get_branches()) self.branch_filter.select(api.get_current_branch(self.store)) self.add_filter(self.branch_filter, position=SearchFilterPosition.TOP) def get_columns(self): return [SearchColumn('code', title=_('Code'), sorted=True, sort_func=sort_sellable_code, data_type=str, width=130), SearchColumn('barcode', title=_("Barcode"), data_type=str, width=130), SearchColumn('category_description', title=_("Category"), data_type=str, width=100, visible=False), SearchColumn('description', title=_("Description"), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('manufacturer', title=_("Manufacturer"), data_type=str, visible=False), SearchColumn('model', title=_("Model"), data_type=str, visible=False), SearchColumn('location', title=_("Location"), data_type=str, width=100, visible=False), SearchColumn('stock', title=_('Quantity'), data_type=decimal.Decimal, width=100), SearchColumn('unit', title=_("Unit"), data_type=str, width=40, visible=False), Column('has_image', title=_('Picture'), data_type=bool, width=80), ] # # Private API # def _query(self, store): branch = self.branch_filter.get_state().value return self.search_table.find_by_branch(store, branch) def _get_branches(self): items = [(b.person.name, b) for b in self.store.find(Branch)] if not items: raise DatabaseInconsistency('You should have at least one ' 'branch on your database.' 'Found zero') items.insert(0, [_('All branches'), None]) return items def _update_widgets(self): branch = api.get_current_branch(self.store) is_main_branch = self.branch_filter.get_state().value is branch item = self.results.get_selected() sellable = item and item.product.sellable if sellable: if sellable.has_image: thumbnail = sellable.image.thumbnail pixbuf = self.pixbuf_converter.from_string(thumbnail) else: pixbuf = None self._update_edit_image(pixbuf) if self.image_viewer: self.image_viewer.set_sellable(sellable) else: self._update_edit_image() self.set_sensitive([self.EditProduct], bool(item)) self.set_sensitive([self.ProductStockHistory], bool(item) and is_main_branch) # We need more than one branch to be able to do transfers # Note that 'all branches' is not a real branch has_branches = len(self.branch_filter.combo) > 2 transfer_active = self.NewTransfer.get_sensitive() self.set_sensitive([self.NewTransfer], transfer_active and has_branches) self.set_sensitive([self.SearchTransfer], has_branches) def _update_edit_image(self, pixbuf=None): if not pixbuf: self.image.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_LARGE_TOOLBAR) return # FIXME: get this icon size from settings icon_size = 24 pixbuf = pixbuf.scale_simple(icon_size, icon_size, gtk.gdk.INTERP_BILINEAR) self.image.set_from_pixbuf(pixbuf) def _update_filter_slave(self, slave): self.refresh() def _transfer_stock(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockTransferWizard, store) store.confirm(model) store.close() self.search.refresh() def _receive_purchase(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(ReceivingOrderWizard, store) store.confirm(model) store.close() self.search.refresh() # # Callbacks # def on_image_viewer_closed(self, window, event): self.StockPictureViewer.props.active = False self.image_viewer = None def on_results__has_rows(self, results, product): self._update_widgets() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_ProductStockHistory__activate(self, button): selected = self.results.get_selected() sellable = selected.sellable self.run_dialog(ProductStockHistoryDialog, self.store, sellable, branch=self.branch_filter.combo.get_selected()) def on_EditProduct__activate(self, button): selected = self.results.get_selected() assert selected store = api.new_store() product = store.fetch(selected.product) model = self.run_dialog(ProductStockEditor, store, product) store.confirm(model) store.close() # Stock def on_NewReceiving__activate(self, button): self._receive_purchase() def on_NewTransfer__activate(self, button): self._transfer_stock() def on_NewStockDecrease__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockDecreaseWizard, store) store.confirm(model) store.close() self.search.refresh() def on_StockInitial__activate(self, action): if self.check_open_inventory(): return branch = self.branch_filter.get_state().value store = api.new_store() retval = self.run_dialog(InitialStockDialog, store, branch) store.confirm(retval) store.close() self.search.refresh() def on_StockPictureViewer__activate(self, button): if self.image_viewer: self.StockPictureViewer.props.active = False self.image_viewer.destroy() self.image_viewer = None else: self.StockPictureViewer.props.active = True self.image_viewer = SellableImageViewer() selected = self.results.get_selected() if selected: self.image_viewer.set_sellable(selected.product.sellable) self.image_viewer.toplevel.connect( "delete-event", self.on_image_viewer_closed) self.image_viewer.toplevel.set_property("visible", True) # Loan def on_LoanNew__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(NewLoanWizard, store) store.confirm(model) store.close() self.search.refresh() def on_LoanClose__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(CloseLoanWizard, store) store.confirm(model) store.close() self.search.refresh() def on_LoanSearch__activate(self, action): self.run_dialog(LoanSearch, self.store) def on_LoanSearchItems__activate(self, action): self.run_dialog(LoanItemSearch, self.store) # Search def on_SearchPurchaseReceiving__activate(self, button): self.run_dialog(PurchaseReceivingSearch, self.store) def on_SearchTransfer__activate(self, action): self.run_dialog(TransferOrderSearch, self.store) def on_SearchPurchasedStockItems__activate(self, action): self.run_dialog(PurchasedItemsSearch, self.store) def on_SearchStockItems__activate(self, action): self.run_dialog(ProductStockSearch, self.store) def on_SearchClosedStockItems__activate(self, action): self.run_dialog(ProductClosedStockSearch, self.store) def on_SearchProductHistory__activate(self, action): self.run_dialog(ProductSearchQuantity, self.store) def on_SearchStockDecrease__activate(self, action): self.run_dialog(StockDecreaseSearch, self.store)
class TillApp(ShellApp): app_title = _(u'Till') gladefile = 'till' search_spec = SaleView search_labels = _(u'matching:') report_table = SalesReport # # Application # def create_actions(self): group = get_accels('app.till') actions = [ ('SaleMenu', None, _('Sale')), ('TillOpen', None, _('Open till...'), group.get('open_till')), ('TillClose', None, _('Close till...'), group.get('close_till')), ('TillVerify', None, _('Verify till...'), group.get('verify_till')), ("TillDailyMovement", None, _("Till daily movement..."), group.get('daily_movement')), ('TillAddCash', None, _('Cash addition...'), ''), ('TillRemoveCash', None, _('Cash removal...'), ''), ("PaymentReceive", None, _("Payment receival..."), group.get('payment_receive'), _("Receive payments")), ("SearchClient", None, _("Clients..."), group.get('search_clients'), _("Search for clients")), ("SearchSale", None, _("Sales..."), group.get('search_sale'), _("Search for sales")), ("SearchCardPayment", None, _("Card payments..."), _("Search for card payments")), ("SearchSoldItemsByBranch", None, _("Sold items by branch..."), group.get('search_sold_items_by_branch'), _("Search for items sold by branch")), ("SearchTillHistory", None, _("Till history..."), group.get('search_till_history'), _("Search for till history")), ("SearchFiscalTillOperations", None, _("Fiscal till operations..."), group.get('search_fiscal_till_operations'), _("Search for fiscal till operations")), ("Confirm", gtk.STOCK_APPLY, _("Confirm..."), group.get('confirm_sale'), _("Confirm the selected sale, decreasing stock and making it " "possible to receive it's payments")), ("Return", gtk.STOCK_CANCEL, _("Return..."), group.get('return_sale'), _("Return the selected sale, returning stock and the client's " "payments")), ("Details", gtk.STOCK_INFO, _("Details..."), group.get('sale_details'), _("Show details of the selected sale")), ] self.till_ui = self.add_ui_actions('', actions, filename="till.xml") self.set_help_section(_("Till help"), 'app-till') self.Confirm.set_short_label(_('Confirm')) self.Return.set_short_label(_('Return')) self.Details.set_short_label(_('Details')) self.Confirm.props.is_important = True self.Return.props.is_important = True self.Details.props.is_important = True def create_ui(self): self.popup = self.uimanager.get_widget('/TillSelection') self.current_branch = api.get_current_branch(self.store) # Groups self.main_vbox.set_focus_chain([self.app_vbox]) self.app_vbox.set_focus_chain([self.search_holder, self.list_vbox]) # Setting up the toolbar self.list_vbox.set_focus_chain([self.footer_hbox]) self._setup_printer() self._setup_widgets() def get_title(self): return _('[%s] - Till') % (api.get_current_branch( self.store).get_description(), ) def activate(self, refresh=True): self.window.add_new_items([self.TillAddCash, self.TillRemoveCash]) self.window.add_search_items([ self.SearchFiscalTillOperations, self.SearchClient, self.SearchSale ]) self.window.Print.set_tooltip(_("Print a report of these sales")) self.refresh() self._printer.run_initial_checks() self.check_open_inventory() def deactivate(self): self.uimanager.remove_ui(self.till_ui) def new_activate(self): if not self.TillAddCash.get_sensitive(): return self._run_add_cash_dialog() def search_activate(self): self._run_search_dialog(TillFiscalOperationsSearch) # # ShellApp # def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.search.set_query(self._query_executer) self.set_text_field_columns( ['client_name', 'salesperson_name', 'identifier_str']) self.status_filter = ComboSearchFilter(_(u"Show orders"), self._get_status_values()) self.status_filter.select(Sale.STATUS_CONFIRMED) self.add_filter(self.status_filter, position=SearchFilterPosition.TOP, columns=['status']) def get_columns(self): return [ IdentifierColumn('identifier', long_title=_('Order #'), sorted=True), Column('status_name', title=_(u'Status'), data_type=str, visible=False), SearchColumn('open_date', title=_('Date Started'), width=110, data_type=date, justify=gtk.JUSTIFY_RIGHT), SearchColumn('client_name', title=_('Client'), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('salesperson_name', title=_('Salesperson'), data_type=str, width=180, ellipsize=pango.ELLIPSIZE_END), SearchColumn('total_quantity', title=_('Quantity'), data_type=decimal.Decimal, width=100, format_func=format_quantity), SearchColumn('total', title=_('Total'), data_type=currency, width=100) ] # # Private # def _query_executer(self, store): # We should only show Sales that # 1) In the current branch (FIXME: Should be on the same station. # See bug 4266) # 2) Are in the status QUOTE or ORDERED. # 3) For the order statuses, the date should be the same as today query = And( Sale.branch == self.current_branch, Or(Sale.status == Sale.STATUS_QUOTE, Sale.status == Sale.STATUS_ORDERED, Date(Sale.open_date) == date.today())) return store.find(self.search_spec, query) def _setup_printer(self): self._printer = FiscalPrinterHelper(self.store, parent=self) self._printer.connect('till-status-changed', self._on_PrinterHelper__till_status_changed) self._printer.connect('ecf-changed', self._on_PrinterHelper__ecf_changed) self._printer.setup_midnight_check() def _get_status_values(self): statuses = [(v, k) for k, v in Sale.statuses.items()] statuses.insert(0, (_('Any'), None)) return statuses def _create_sale_payments(self, order_view): store = api.new_store() # TODO: Maybe change the sale status to ORDERED sale = store.fetch(order_view.sale) retval = run_dialog(SalePaymentsEditor, self, store, sale) if store.confirm(retval): self.refresh() store.close() def _confirm_order(self, order_view): if self.check_open_inventory(): return store = api.new_store() sale = store.fetch(order_view.sale) expire_date = sale.expire_date if (sale.status == Sale.STATUS_QUOTE and expire_date and expire_date.date() < date.today() and not yesno( _("This quote has expired. Confirm it anyway?"), gtk.RESPONSE_YES, _("Confirm quote"), _("Don't confirm"))): store.close() return missing = get_missing_items(sale, store) if missing: retval = run_dialog(MissingItemsDialog, self, sale, missing) if retval: self.refresh() store.close() return coupon = self._open_coupon() if not coupon: store.close() return subtotal = self._add_sale_items(sale, coupon) try: if coupon.confirm(sale, store, subtotal=subtotal): workorders = WorkOrder.find_by_sale(store, sale) for order in workorders: order.close() store.commit() self.refresh() else: coupon.cancel() except SellError as err: warning(str(err)) except ModelDataError as err: warning(str(err)) store.close() def _open_coupon(self): coupon = self._printer.create_coupon() if coupon: while not coupon.open(): if not yesno( _("Failed to open the fiscal coupon.\n" "Until it is opened, it's not possible to " "confirm the sale. Do you want to try again?"), gtk.RESPONSE_YES, _("Try again"), _("Cancel coupon")): return None return coupon def _add_sale_items(self, sale, coupon): subtotal = 0 for sale_item in sale.get_items(): coupon.add_item(sale_item) subtotal += sale_item.price * sale_item.quantity return subtotal def _update_total(self): balance = currency(self._get_till_balance()) text = _(u"Total: %s") % converter.as_string(currency, balance) self.total_label.set_text(text) def _get_till_balance(self): """Returns the balance of till operations""" try: till = Till.get_current(self.store) except TillError: till = None if till is None: return currency(0) return till.get_balance() def _setup_widgets(self): # SearchSale is here because it's possible to return a sale inside it self._inventory_widgets = [self.Confirm, self.SearchSale, self.Return] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.total_label.set_size('xx-large') self.total_label.set_bold(True) self.till_status_label.set_size('xx-large') self.till_status_label.set_bold(True) def _update_toolbar_buttons(self): sale_view = self.results.get_selected() if sale_view: can_confirm = sale_view.can_confirm() # when confirming sales in till, we also might want to cancel # sales can_return = (sale_view.can_return() or sale_view.can_cancel()) else: can_confirm = can_return = False self.set_sensitive([self.Details], bool(sale_view)) self.set_sensitive([self.Confirm], can_confirm) self.set_sensitive([self.Return], can_return) def _check_selected(self): sale_view = self.results.get_selected() if not sale_view: raise StoqlibError("You should have a selected item at " "this point") return sale_view def _run_search_dialog(self, dialog_type, **kwargs): store = api.new_store() self.run_dialog(dialog_type, store, **kwargs) store.close() def _run_details_dialog(self): sale_view = self._check_selected() run_dialog(SaleDetailsDialog, self, self.store, sale_view) def _run_add_cash_dialog(self): with api.trans() as store: try: run_dialog(CashInEditor, self, store) except TillError as err: # Inform the error to the user instead of crashing warning(str(err)) return if store.committed: self._update_total() def _return_sale(self): if self.check_open_inventory(): return sale_view = self._check_selected() with api.trans() as store: return_sale(self.get_toplevel(), store.fetch(sale_view.sale), store) if store.committed: self._update_total() self.refresh() def _update_ecf(self, has_ecf): # If we have an ecf, let the other events decide what to disable. if has_ecf: return # We dont have an ecf. Disable till related operations widgets = [ self.TillOpen, self.TillClose, self.TillVerify, self.TillAddCash, self.TillRemoveCash, self.SearchTillHistory, self.app_vbox, self.Confirm, self.Return, self.Details ] self.set_sensitive(widgets, has_ecf) text = _(u"Till operations requires a connected fiscal printer") self.till_status_label.set_text(text) def _update_till_status(self, closed, blocked): # Three different situations; # # - Till is closed # - Till is opened # - Till was not closed the previous fiscal day (blocked) self.set_sensitive([self.TillOpen], closed) self.set_sensitive([self.TillClose, self.PaymentReceive], not closed or blocked) widgets = [ self.TillVerify, self.TillAddCash, self.TillRemoveCash, self.SearchTillHistory, self.app_vbox ] self.set_sensitive(widgets, not closed and not blocked) if closed: text = _(u"Till closed") self.clear() self.setup_focus() elif blocked: text = _(u"Till blocked from previous day") else: till = Till.get_current(self.store) text = _(u"Till opened on %s") % till.opening_date.strftime('%x') self.till_status_label.set_text(text) self._update_toolbar_buttons() self._update_total() # # Callbacks # def on_Confirm__activate(self, action): selected = self.results.get_selected() query = WorkOrder.status != WorkOrder.STATUS_WORK_FINISHED # If there are unfinished workorders associated with the sale, we # cannot print the coupon yet. Instead lets just create the payments. unfinished = WorkOrder.find_by_sale(self.store, selected.sale).find(query) if not unfinished.is_empty(): self._create_sale_payments(selected) else: self._confirm_order(selected) self._update_total() def on_results__double_click(self, results, sale): self._run_details_dialog() def on_results__selection_changed(self, results, sale): self._update_toolbar_buttons() def on_results__has_rows(self, results, has_rows): self._update_total() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_Details__activate(self, action): self._run_details_dialog() def on_Return__activate(self, action): self._return_sale() def _on_PrinterHelper__till_status_changed(self, printer, closed, blocked): self._update_till_status(closed, blocked) def _on_PrinterHelper__ecf_changed(self, printer, ecf): self._update_ecf(ecf) def on_PaymentReceive__activate(self, action): self.run_dialog(PaymentReceivingSearch, self.store) # Till def on_TillVerify__activate(self, button): self._printer.verify_till() def on_TillClose__activate(self, button): self._printer.close_till() def on_TillOpen__activate(self, button): self._printer.open_till() def on_TillAddCash__activate(self, action): self._run_add_cash_dialog() def on_TillRemoveCash__activate(self, action): with api.trans() as store: run_dialog(CashOutEditor, self, store) if store.committed: self._update_total() def on_TillDailyMovement__activate(self, button): today = localtoday() print_report(TillDailyMovementReport, self.store, today) # Search def on_SearchClient__activate(self, action): self._run_search_dialog(ClientSearch, hide_footer=True) def on_SearchSale__activate(self, action): if self.check_open_inventory(): return self._run_search_dialog(SaleWithToolbarSearch) self.refresh() def on_SearchCardPayment__activate(self, action): self.run_dialog(CardPaymentSearch, self.store) def on_SearchSoldItemsByBranch__activate(self, button): self._run_search_dialog(SoldItemsByBranchSearch) def on_SearchTillHistory__activate(self, button): self.run_dialog(TillHistoryDialog, self.store) def on_SearchFiscalTillOperations__activate(self, button): self._run_search_dialog(TillFiscalOperationsSearch)
def create_filters(self): statuses = [(v, k) for k, v in Client.statuses.items()] statuses.insert(0, (_("Any"), None)) status_filter = ComboSearchFilter(_("Show clients with status"), statuses) status_filter.select(None) self.add_filter(status_filter, SearchFilterPosition.TOP, ["status"])
class TillApp(ShellApp): app_title = _(u'Till') gladefile = 'till' search_spec = SaleView search_labels = _(u'matching:') report_table = SalesReport # # Application # def create_actions(self): group = get_accels('app.till') actions = [ ('SaleMenu', None, _('Sale')), ('TillOpen', None, _('Open till...'), group.get('open_till')), ('TillClose', None, _('Close till...'), group.get('close_till')), ('TillVerify', None, _('Verify till...'), group.get('verify_till')), ('TillAddCash', None, _('Cash addition...'), ''), ('TillRemoveCash', None, _('Cash removal...'), ''), ("PaymentReceive", None, _("Payment receival..."), group.get('payment_receive'), _("Receive payments")), ("SearchClient", None, _("Clients..."), group.get('search_clients'), _("Search for clients")), ("SearchSale", None, _("Sales..."), group.get('search_sale'), _("Search for sales")), ("SearchSoldItemsByBranch", None, _("Sold items by branch..."), group.get('search_sold_items_by_branch'), _("Search for items sold by branch")), ("SearchTillHistory", None, _("Till history..."), group.get('search_till_history'), _("Search for till history")), ("SearchFiscalTillOperations", None, _("Fiscal till operations..."), group.get('search_fiscal_till_operations'), _("Search for fiscal till operations")), ("Confirm", gtk.STOCK_APPLY, _("Confirm..."), group.get('confirm_sale'), _("Confirm the selected sale, decreasing stock and making it " "possible to receive it's payments")), ("Return", gtk.STOCK_CANCEL, _("Return..."), group.get('return_sale'), _("Return the selected sale, returning stock and the client's " "payments")), ("Details", gtk.STOCK_INFO, _("Details..."), group.get('sale_details'), _("Show details of the selected sale")), ] self.till_ui = self.add_ui_actions('', actions, filename="till.xml") self.set_help_section(_("Till help"), 'app-till') self.Confirm.set_short_label(_('Confirm')) self.Return.set_short_label(_('Return')) self.Details.set_short_label(_('Details')) self.Confirm.props.is_important = True self.Return.props.is_important = True self.Details.props.is_important = True def create_ui(self): self.popup = self.uimanager.get_widget('/TillSelection') self.current_branch = api.get_current_branch(self.store) # Groups self.main_vbox.set_focus_chain([self.app_vbox]) self.app_vbox.set_focus_chain([self.search_holder, self.list_vbox]) # Setting up the toolbar self.list_vbox.set_focus_chain([self.footer_hbox]) self._setup_printer() self._setup_widgets() def get_title(self): return _('Stoq - Till for Branch %03d') % ( api.get_current_branch(self.store).id, ) def activate(self, params): self.window.add_new_items([self.TillAddCash, self.TillRemoveCash]) self.window.add_search_items([self.SearchFiscalTillOperations, self.SearchClient, self.SearchSale]) self.window.Print.set_tooltip(_("Print a report of these sales")) self.refresh() self._printer.run_initial_checks() self.check_open_inventory() def deactivate(self): self.uimanager.remove_ui(self.till_ui) def new_activate(self): if not self.TillAddCash.get_sensitive(): return self._run_add_cash_dialog() def search_activate(self): self._run_search_dialog(TillFiscalOperationsSearch) # # ShellApp # def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.search.set_query(self._query_executer) self.set_text_field_columns(['client_name', 'salesperson_name']) self.status_filter = ComboSearchFilter(_(u"Show orders"), self._get_status_values()) self.status_filter.select(Sale.STATUS_CONFIRMED) self.add_filter(self.status_filter, position=SearchFilterPosition.TOP, columns=['status']) def get_columns(self): return [IdentifierColumn('identifier', long_title=_('Order #'), sorted=True), Column('status_name', title=_(u'Status'), data_type=str, visible=False), SearchColumn('open_date', title=_('Date Started'), width=110, data_type=date, justify=gtk.JUSTIFY_RIGHT), SearchColumn('client_name', title=_('Client'), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('salesperson_name', title=_('Salesperson'), data_type=str, width=180, ellipsize=pango.ELLIPSIZE_END), SearchColumn('total_quantity', title=_('Quantity'), data_type=decimal.Decimal, width=100, format_func=format_quantity), SearchColumn('total', title=_('Total'), data_type=currency, width=100)] # # Private # def _query_executer(self, store): # We should only show Sales that # 1) In the current branch (FIXME: Should be on the same station. # See bug 4266) # 2) Are in the status QUOTE or ORDERED. # 3) For the order statuses, the date should be the same as today query = And(Sale.branch == self.current_branch, Or(Sale.status == Sale.STATUS_QUOTE, Sale.status == Sale.STATUS_ORDERED, Date(Sale.open_date) == date.today())) return store.find(self.search_spec, query) def _setup_printer(self): self._printer = FiscalPrinterHelper(self.store, parent=self) self._printer.connect('till-status-changed', self._on_PrinterHelper__till_status_changed) self._printer.connect('ecf-changed', self._on_PrinterHelper__ecf_changed) self._printer.setup_midnight_check() def _get_status_values(self): statuses = [(v, k) for k, v in Sale.statuses.items()] statuses.insert(0, (_('Any'), None)) return statuses def _confirm_order(self): if self.check_open_inventory(): return store = api.new_store() selected = self.results.get_selected() sale = store.fetch(selected.sale) expire_date = sale.expire_date if (sale.status == Sale.STATUS_QUOTE and expire_date and expire_date.date() < date.today() and not yesno(_("This quote has expired. Confirm it anyway?"), gtk.RESPONSE_YES, _("Confirm quote"), _("Don't confirm"))): store.close() return missing = get_missing_items(sale, store) if missing: retval = run_dialog(MissingItemsDialog, self, sale, missing) if retval: self.refresh() store.close() return coupon = self._open_coupon() if not coupon: store.close() return subtotal = self._add_sale_items(sale, coupon) try: if coupon.confirm(sale, store, subtotal=subtotal): store.commit() self.refresh() else: coupon.cancel() except SellError as err: warning(str(err)) except ModelDataError as err: warning(str(err)) store.close() def _open_coupon(self): coupon = self._printer.create_coupon() if coupon: while not coupon.open(): if not yesno(_("Failed to open the fiscal coupon.\n" "Until it is opened, it's not possible to " "confirm the sale. Do you want to try again?"), gtk.RESPONSE_YES, _("Try again"), _("Cancel coupon")): return None return coupon def _add_sale_items(self, sale, coupon): subtotal = 0 for sale_item in sale.get_items(): coupon.add_item(sale_item) subtotal += sale_item.price * sale_item.quantity return subtotal def _update_total(self): balance = currency(self._get_till_balance()) text = _(u"Total: %s") % converter.as_string(currency, balance) self.total_label.set_text(text) def _get_till_balance(self): """Returns the balance of till operations""" try: till = Till.get_current(self.store) except TillError: till = None if till is None: return currency(0) return till.get_balance() def _setup_widgets(self): # SearchSale is here because it's possible to return a sale inside it self._inventory_widgets = [self.Confirm, self.SearchSale, self.Return] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.total_label.set_size('xx-large') self.total_label.set_bold(True) self.till_status_label.set_size('xx-large') self.till_status_label.set_bold(True) def _update_toolbar_buttons(self): sale_view = self.results.get_selected() if sale_view: can_confirm = sale_view.can_confirm() # when confirming sales in till, we also might want to cancel # sales can_return = (sale_view.can_return() or sale_view.can_cancel()) else: can_confirm = can_return = False self.set_sensitive([self.Details], bool(sale_view)) self.set_sensitive([self.Confirm], can_confirm) self.set_sensitive([self.Return], can_return) def _check_selected(self): sale_view = self.results.get_selected() if not sale_view: raise StoqlibError("You should have a selected item at " "this point") return sale_view def _run_search_dialog(self, dialog_type, **kwargs): store = api.new_store() self.run_dialog(dialog_type, store, **kwargs) store.close() def _run_details_dialog(self): sale_view = self._check_selected() run_dialog(SaleDetailsDialog, self, self.store, sale_view) def _run_add_cash_dialog(self): with api.trans() as store: try: run_dialog(CashInEditor, self, store) except TillError as err: # Inform the error to the user instead of crashing warning(str(err)) return if store.committed: self._update_total() def _return_sale(self): if self.check_open_inventory(): return sale_view = self._check_selected() with api.trans() as store: return_sale(self.get_toplevel(), store.fetch(sale_view.sale), store) if store.committed: self._update_total() self.refresh() def _update_ecf(self, has_ecf): # If we have an ecf, let the other events decide what to disable. if has_ecf: return # We dont have an ecf. Disable till related operations widgets = [self.TillOpen, self.TillClose, self.TillVerify, self.TillAddCash, self.TillRemoveCash, self.SearchTillHistory, self.app_vbox, self.Confirm, self.Return, self.Details] self.set_sensitive(widgets, has_ecf) text = _(u"Till operations requires a connected fiscal printer") self.till_status_label.set_text(text) def _update_till_status(self, closed, blocked): # Three different situations; # # - Till is closed # - Till is opened # - Till was not closed the previous fiscal day (blocked) self.set_sensitive([self.TillOpen], closed) self.set_sensitive([self.TillClose, self.PaymentReceive], not closed or blocked) widgets = [self.TillVerify, self.TillAddCash, self.TillRemoveCash, self.SearchTillHistory, self.app_vbox] self.set_sensitive(widgets, not closed and not blocked) if closed: text = _(u"Till closed") self.clear() self.setup_focus() elif blocked: text = _(u"Till blocked from previous day") else: till = Till.get_current(self.store) text = _(u"Till opened on %s") % till.opening_date.strftime('%x') self.till_status_label.set_text(text) self._update_toolbar_buttons() self._update_total() # # Callbacks # def on_Confirm__activate(self, action): self._confirm_order() self._update_total() def on_results__double_click(self, results, sale): self._run_details_dialog() def on_results__selection_changed(self, results, sale): self._update_toolbar_buttons() def on_results__has_rows(self, results, has_rows): self._update_total() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_Details__activate(self, action): self._run_details_dialog() def on_Return__activate(self, action): self._return_sale() def _on_PrinterHelper__till_status_changed(self, printer, closed, blocked): self._update_till_status(closed, blocked) def _on_PrinterHelper__ecf_changed(self, printer, ecf): self._update_ecf(ecf) def on_PaymentReceive__activate(self, action): self.run_dialog(PaymentReceivingSearch, self.store) # Till def on_TillVerify__activate(self, button): self._printer.verify_till() def on_TillClose__activate(self, button): self._printer.close_till() def on_TillOpen__activate(self, button): self._printer.open_till() def on_TillAddCash__activate(self, action): self._run_add_cash_dialog() def on_TillRemoveCash__activate(self, action): with api.trans() as store: run_dialog(CashOutEditor, self, store) if store.committed: self._update_total() # Search def on_SearchClient__activate(self, action): self._run_search_dialog(ClientSearch, hide_footer=True) def on_SearchSale__activate(self, action): if self.check_open_inventory(): return self._run_search_dialog(SaleWithToolbarSearch) self.refresh() def on_SearchSoldItemsByBranch__activate(self, button): self._run_search_dialog(SoldItemsByBranchSearch) def on_SearchTillHistory__activate(self, button): self.run_dialog(TillHistoryDialog, self.store) def on_SearchFiscalTillOperations__activate(self, button): self._run_search_dialog(TillFiscalOperationsSearch)
class DeliverySearch(SearchEditor): """Delivery search implementation""" title = _('Delivery Search') search_spec = DeliveryView editor_class = DeliveryEditor has_new_button = False size = (750, 450) # # SearchEditor hooks # def _get_status_values(self): items = [(value, key) for key, value in Delivery.statuses.items()] items.insert(0, (_('Any'), None)) return items def create_filters(self): self.set_text_field_columns( ['tracking_code', 'transporter_name', 'client_name']) # Status statuses = [(desc, st) for st, desc in Delivery.statuses.items()] statuses.insert(0, (_('Any'), None)) self.status_filter = ComboSearchFilter(_('With status:'), statuses) self.status_filter.select(None) self.add_filter(self.status_filter, columns=['status'], position=SearchFilterPosition.TOP) def get_editor_model(self, viewable): return viewable.delivery def get_columns(self): return [ IdentifierColumn('sale_identifier', title=_('Sale #'), order=gtk.SORT_DESCENDING), SearchColumn('status_str', title=_('Status'), data_type=str, search_attribute='status', valid_values=self._get_status_values()), Column('address_str', title=_('Address'), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('tracking_code', title=_('Tracking code'), data_type=str), SearchColumn('transporter_name', title=_('Transporter'), data_type=str), SearchColumn('client_name', title=_('Client'), data_type=str), SearchColumn('open_date', title=_('Open date'), data_type=datetime.date, visible=False), SearchColumn('deliver_date', title=_('Sent date'), data_type=datetime.date, visible=False), SearchColumn('receive_date', title=_('Received date'), data_type=datetime.date, visible=False), ]
class StockApp(ShellApp): app_title = _('Stock') gladefile = "stock" search_spec = ProductFullStockView search_labels = _('Matching:') report_table = SimpleProductReport pixbuf_converter = converter.get_converter(gtk.gdk.Pixbuf) # # Application # def create_actions(self): group = get_accels('app.stock') actions = [ ("NewReceiving", STOQ_RECEIVING, _("Order _receival..."), group.get('new_receiving')), ('NewTransfer', gtk.STOCK_CONVERT, _('Transfer...'), group.get('transfer_product')), ('NewStockDecrease', None, _('Stock decrease...'), group.get('stock_decrease')), ('StockInitial', gtk.STOCK_GO_UP, _('Register initial stock...')), ("LoanNew", None, _("Loan...")), ("LoanClose", None, _("Close loan...")), ("SearchPurchaseReceiving", None, _("Received purchases..."), group.get('search_receiving'), _("Search for received purchase orders")), ("SearchProductHistory", None, _("Product history..."), group.get('search_product_history'), _("Search for product history")), ("SearchStockDecrease", None, _("Stock decreases..."), '', _("Search for manual stock decreases")), ("SearchPurchasedStockItems", None, _("Purchased items..."), group.get('search_purchased_stock_items'), _("Search for purchased items")), ("SearchBrandItems", None, _("Brand items..."), group.get('search_brand_items'), _("Search for brand items on stock")), ("SearchBrandItemsByBranch", None, _("Brand item by branch..."), group.get('search_brand_by_branch'), _("Search for brand items by branch on stock")), ("SearchBatchItems", None, _("Batch items..."), group.get('search_batch_items'), _("Search for batch items on stock")), ("SearchStockItems", None, _("Stock items..."), group.get('search_stock_items'), _("Search for items on stock")), ("SearchTransfer", None, _("Transfers..."), group.get('search_transfers'), _("Search for stock transfers")), ("SearchClosedStockItems", None, _("Closed stock Items..."), group.get('search_closed_stock_items'), _("Search for closed stock items")), ("LoanSearch", None, _("Loans...")), ("LoanSearchItems", None, _("Loan items...")), ("SearchTransferItems", None, _("Transfer items...")), ("SearchReturnedItems", None, _("Returned items...")), ("SearchPendingReturnedSales", None, _("Pending returned sales...")), ("ProductMenu", None, _("Product")), ("PrintLabels", None, _("Print labels...")), ("ProductStockHistory", gtk.STOCK_INFO, _("History..."), group.get('history'), _('Show the stock history of the selected product')), ("EditProduct", gtk.STOCK_EDIT, _("Edit..."), group.get('edit_product'), _("Edit the selected product, allowing you to change it's " "details")), ] self.stock_ui = self.add_ui_actions('', actions, filename='stock.xml') toggle_actions = [ ('StockPictureViewer', None, _('Picture viewer'), group.get('toggle_picture_viewer')), ] self.add_ui_actions('', toggle_actions, 'ToggleActions', 'toggle') self.set_help_section(_("Stock help"), 'app-stock') self.NewReceiving.set_short_label(_("Receive")) self.NewTransfer.set_short_label(_("Transfer")) self.EditProduct.set_short_label(_("Edit")) self.ProductStockHistory.set_short_label(_("History")) self.EditProduct.props.is_important = True self.ProductStockHistory.props.is_important = True def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.popup = self.uimanager.get_widget('/StockSelection') self.window.add_new_items([self.NewReceiving, self.NewTransfer, self.NewStockDecrease, self.LoanNew]) self.window.add_search_items([ self.SearchStockItems, self.SearchBrandItems, self.SearchStockDecrease, self.SearchClosedStockItems, self.SearchProductHistory, self.SearchPurchasedStockItems, self.SearchTransfer, ]) self.window.Print.set_tooltip( _("Print a report of these products")) self._inventory_widgets = [self.NewTransfer, self.NewReceiving, self.StockInitial, self.NewStockDecrease, self.LoanNew, self.LoanClose] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.image_viewer = None self.image = gtk.Image() self.edit_button = self.uimanager.get_widget('/toolbar/AppToolbarPH/EditProduct') self.edit_button.set_icon_widget(self.image) self.image.show() self.search.set_summary_label(column='stock', label=_('<b>Stock Total:</b>'), format='<b>%s</b>', parent=self.get_statusbar_message_area()) def activate(self, refresh=True): self.window.NewToolItem.set_tooltip( _("Create a new receiving order")) self.window.SearchToolItem.set_tooltip( _("Search for stock items")) if refresh: self.refresh() open_inventory = self.check_open_inventory() if not open_inventory: self.transfers_bar = self._create_pending_info_message() self.returned_bar = self._create_pending_returned_sale_message() else: self.transfers_bar = None self.returned_bar = None self._update_widgets() self.search.focus_search_entry() def deactivate(self): if self.transfers_bar: self.transfers_bar.hide() if self.returned_bar: self.returned_bar.hide() self.uimanager.remove_ui(self.stock_ui) self._close_image_viewer() def new_activate(self): if not self.NewReceiving.get_sensitive(): warning(_("You cannot receive a purchase with an open inventory.")) return self._receive_purchase() def search_activate(self): self.run_dialog(ProductStockSearch, self.store) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.search.set_query(self._query) self.set_text_field_columns(['description', 'code', 'barcode', 'category_description', 'manufacturer']) branches = Branch.get_active_branches(self.store) self.branch_filter = ComboSearchFilter( _('Show by:'), api.for_combo(branches, empty=_("All branches"))) self.branch_filter.select(api.get_current_branch(self.store)) self.add_filter(self.branch_filter, position=SearchFilterPosition.TOP) def get_columns(self): return [SearchColumn('code', title=_('Code'), sorted=True, sort_func=sort_sellable_code, data_type=str, width=130), SearchColumn('barcode', title=_("Barcode"), data_type=str, width=130), SearchColumn('category_description', title=_("Category"), data_type=str, width=100, visible=False), SearchColumn('description', title=_("Description"), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('manufacturer', title=_("Manufacturer"), data_type=str, visible=False), SearchColumn('brand', title=_("Brand"), data_type=str, visible=False), SearchColumn('model', title=_("Model"), data_type=str, visible=False), SearchColumn('location', title=_("Location"), data_type=str, width=100, visible=False), QuantityColumn('stock', title=_('Quantity'), width=100, use_having=True), SearchColumn('has_image', title=_('Picture'), data_type=bool, width=80), ] # # Private API # def _open_image_viewer(self): assert self.image_viewer is None self.image_viewer = SellableImageViewer(size=(325, 325)) self.image_viewer.toplevel.connect( 'delete-event', self.on_image_viewer_closed) self.image_viewer.show_all() self._update_widgets() def _close_image_viewer(self): if self.image_viewer is None: return self.image_viewer.destroy() self.image_viewer = None def _query(self, store): branch = self.branch_filter.get_state().value return self.search_spec.find_by_branch(store, branch) def _update_widgets(self): branch = api.get_current_branch(self.store) is_main_branch = self.branch_filter.get_state().value is branch item = self.results.get_selected() sellable = item and item.product.sellable if sellable: if item.has_image: thumbnail = sellable.image.thumbnail pixbuf = self.pixbuf_converter.from_string(thumbnail) else: pixbuf = None self._update_edit_image(pixbuf) if self.image_viewer: self.image_viewer.set_sellable(sellable) else: self._update_edit_image() self.set_sensitive([self.EditProduct, self.PrintLabels], bool(item)) self.set_sensitive([self.ProductStockHistory], bool(item) and is_main_branch) # We need more than one branch to be able to do transfers # Note that 'all branches' is not a real branch has_branches = len(self.branch_filter.combo) > 2 transfer_active = self.NewTransfer.get_sensitive() self.set_sensitive([self.NewTransfer], transfer_active and has_branches) # Building a list of searches that we must disable if there is no # branches other than the main company searches = [self.SearchTransfer, self.SearchTransferItems, self.SearchPendingReturnedSales] self.set_sensitive(searches, has_branches) def _update_edit_image(self, pixbuf=None): if not pixbuf: self.image.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_LARGE_TOOLBAR) return # FIXME: get this icon size from settings icon_size = 24 pixbuf = pixbuf.scale_simple(icon_size, icon_size, gtk.gdk.INTERP_BILINEAR) self.image.set_from_pixbuf(pixbuf) def _update_filter_slave(self, slave): self.refresh() def _transfer_stock(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockTransferWizard, store) store.confirm(model) store.close() self.refresh() def _receive_purchase(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(ReceivingOrderWizard, store) store.confirm(model) store.close() self.refresh() def _create_pending_info_message(self): branch = api.get_current_branch(self.store) n_transfers = TransferOrder.get_pending_transfers(self.store, branch).count() if not n_transfers: return None msg = stoqlib_ngettext(_(u"You have %s incoming transfer"), _(u"You have %s incoming transfers"), n_transfers) % n_transfers info_bar = self.window.add_info_bar(gtk.MESSAGE_QUESTION, msg) button = info_bar.add_button(_(u"Receive"), gtk.RESPONSE_OK) button.connect('clicked', self._on_info_transfers__clicked) return info_bar def _create_pending_returned_sale_message(self): branch = api.get_current_branch(self.store) n_returned = ReturnedSale.get_pending_returned_sales(self.store, branch).count() if not n_returned: return None msg = stoqlib_ngettext(_(u"You have %s returned sale to receive"), _(u"You have %s returned sales to receive"), n_returned) % n_returned info_returned_bar = self.window.add_info_bar(gtk.MESSAGE_QUESTION, msg) button = info_returned_bar.add_button(_(u"Returned sale"), gtk.RESPONSE_OK) button.connect('clicked', self._on_info_returned_sales__clicked) return info_returned_bar def _search_transfers(self): branch = api.get_current_branch(self.store) self.run_dialog(TransferOrderSearch, self.store) # After the search is closed we may want to update , or even hide the # message, if there is no pending transfer to receive if self.transfers_bar: n_transfers = TransferOrder.get_pending_transfers(self.store, branch).count() if n_transfers > 0: msg = stoqlib_ngettext(_(u"You have %s incoming transfer"), _(u"You have %s incoming transfers"), n_transfers) % n_transfers self.transfers_bar.set_message(msg) else: self.transfers_bar.hide() self.refresh() def _search_pending_returned_sales(self): with api.new_store() as store: self.run_dialog(PendingReturnedSaleSearch, store) branch = api.get_current_branch(self.store) # After the search is closed we may want to update , or even hide the # message, if there is no pending returned sale to receive if self.returned_bar: n_returned = ReturnedSale.get_pending_returned_sales(self.store, branch).count() if n_returned > 0: msg = stoqlib_ngettext(_(u"You have %s returned sale to receive"), _(u"You have %s returned sales to receive"), n_returned) % n_returned self.returned_bar.set_message(msg) else: self.returned_bar.hide() self.refresh() # # Callbacks # def on_image_viewer_closed(self, window, event): self.image_viewer = None self.StockPictureViewer.set_active(False) def on_results__has_rows(self, results, product): self._update_widgets() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_ProductStockHistory__activate(self, button): selected = self.results.get_selected() sellable = selected.sellable self.run_dialog(ProductStockHistoryDialog, self.store, sellable, branch=self.branch_filter.combo.get_selected()) def on_PrintLabels__activate(self, button): selected = self.results.get_selected() sellable = selected.sellable label_data = self.run_dialog(PrintLabelEditor, None, self.store, sellable) if label_data: print_labels(label_data, self.store) def on_EditProduct__activate(self, button): selected = self.results.get_selected() assert selected store = api.new_store() product = store.fetch(selected.product) model = self.run_dialog(ProductStockEditor, store, product) store.confirm(model) store.close() if model: self.refresh() def _on_info_transfers__clicked(self, button): self._search_transfers() def _on_info_returned_sales__clicked(self, button): self._search_pending_returned_sales() # Stock def on_NewReceiving__activate(self, button): self._receive_purchase() def on_NewTransfer__activate(self, button): self._transfer_stock() def on_NewStockDecrease__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockDecreaseWizard, store) store.confirm(model) store.close() self.refresh() def on_StockInitial__activate(self, action): if self.check_open_inventory(): return with api.new_store() as store: self.run_dialog(InitialStockDialog, store) if store.committed: self.refresh() def on_StockPictureViewer__toggled(self, button): if button.get_active(): self._open_image_viewer() else: self._close_image_viewer() # Loan def on_LoanNew__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(NewLoanWizard, store) store.confirm(model) store.close() self.refresh() def on_LoanClose__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(CloseLoanWizard, store) store.confirm(model) store.close() self.refresh() def on_LoanSearch__activate(self, action): self.run_dialog(LoanSearch, self.store) def on_LoanSearchItems__activate(self, action): self.run_dialog(LoanItemSearch, self.store) # Search def on_SearchPurchaseReceiving__activate(self, button): self.run_dialog(PurchaseReceivingSearch, self.store) def on_SearchTransfer__activate(self, action): self._search_transfers() def on_SearchTransferItems__activate(self, action): self.run_dialog(TransferItemSearch, self.store) def on_SearchPendingReturnedSales__activate(self, action): self._search_pending_returned_sales() def on_SearchReturnedItems__activate(self, action): self.run_dialog(ReturnedItemSearch, self.store) def on_SearchPurchasedStockItems__activate(self, action): self.run_dialog(PurchasedItemsSearch, self.store) def on_SearchStockItems__activate(self, action): self.run_dialog(ProductStockSearch, self.store) def on_SearchBrandItems__activate(self, action): self.run_dialog(ProductBrandSearch, self.store) def on_SearchBrandItemsByBranch__activate(self, action): self.run_dialog(ProductBrandByBranchSearch, self.store) def on_SearchBatchItems__activate(self, action): self.run_dialog(ProductBatchSearch, self.store) def on_SearchClosedStockItems__activate(self, action): self.run_dialog(ProductClosedStockSearch, self.store) def on_SearchProductHistory__activate(self, action): self.run_dialog(ProductSearchQuantity, self.store) def on_SearchStockDecrease__activate(self, action): self.run_dialog(StockDecreaseSearch, self.store)
class InventoryApp(ShellApp): # TODO: Change all widget.set_sensitive to self.set_sensitive([widget]) app_title = _('Inventory') gladefile = "inventory" search_spec = Inventory search_labels = _('Matching:') report_table = InventoryReport # # Application # def create_actions(self): group = get_accels('app.inventory') actions = [ # File ('NewInventory', None, _('Inventory...'), group.get('new_inventory'), _('Create a new inventory for product counting')), # Inventory ('InventoryMenu', None, _('Inventory')), ('Details', gtk.STOCK_INFO, _('Details...'), group.get('inventory_details'), _('See details about this inventory')), ('CountingAction', gtk.STOCK_INDEX, _('_Count...'), group.get('inventory_count'), _('Register the actual stock of products in the selected ' 'inventory')), ('AdjustAction', gtk.STOCK_CONVERT, _('_Adjust...'), group.get('inventory_adjust'), _('Adjust the stock accordingly to the counting in the selected ' 'inventory')), ('Cancel', gtk.STOCK_CANCEL, _('Cancel...'), group.get('inventory_cancel'), _('Cancel the selected inventory')), ('PrintProductListing', gtk.STOCK_PRINT, _('Print product listing...'), group.get('inventory_print'), _('Print the product listing for this inventory')) ] self.inventory_ui = self.add_ui_actions('', actions, filename='inventory.xml') self.set_help_section(_("Inventory help"), 'app-inventory') self.AdjustAction.set_short_label(_("Adjust")) self.CountingAction.set_short_label(_("Count")) self.Details.set_short_label(_("Details")) self.Cancel.set_short_label(_("Cancel")) self.AdjustAction.props.is_important = True self.CountingAction.props.is_important = True self.Details.props.is_important = True self.Cancel.props.is_important = True def create_ui(self): self.popup = self.uimanager.get_widget('/InventorySelection') self.window.add_new_items([self.NewInventory]) self.window.Print.set_tooltip( _("Print a report of these inventories")) def activate(self, refresh=True): # Avoid letting this sensitive if has-rows is never emitted self.refresh() self._update_widgets() self.window.SearchToolItem.set_sensitive(False) def deactivate(self): self.uimanager.remove_ui(self.inventory_ui) self.window.SearchToolItem.set_sensitive(True) def new_activate(self): if not self.NewInventory.get_sensitive(): warning(_("You cannot open an inventory without having a " "branch with stock in it.")) return self._open_inventory() def create_filters(self): # Disable string search right now, until we use a proper Viewable # for this application self.search.disable_search_entry() self.branch_filter = ComboSearchFilter(_('Show inventories at:'), self._get_branches_for_filter()) current = api.get_current_branch(self.store) self.branch_filter.select(current.id) self.add_filter(self.branch_filter, SearchFilterPosition.TOP, columns=["branch_id"]) def get_columns(self): return [IdentifierColumn('identifier', title=_('Code'), sorted=True, order=gtk.SORT_DESCENDING), SearchColumn('status_str', title=_('Status'), data_type=str, width=100, valid_values=self._get_status_values(), search_attribute='status'), Column('branch.person.name', title=_('Branch'), data_type=str, expand=True), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), data_type=datetime.date, width=120), SearchColumn('close_date', title=_('Closed'), long_title=_('Date Closed'), data_type=datetime.date, width=120)] # # Private API # def _get_status_values(self): values = [(v, k) for k, v in Inventory.statuses.items()] values.insert(0, (_("Any"), None)) return values def _get_branches(self): return self.store.find(Branch) def _get_branches_for_filter(self): items = [(b.person.name, b.id) for b in self._get_branches()] if not items: raise DatabaseInconsistency('You should have at least one ' 'branch on your database.' 'Found zero') items.insert(0, [_('All branches'), None]) return items def _update_widgets(self): has_open = False all_counted = False has_adjusted = False selected = self.results.get_selected() if selected: all_counted = selected.all_items_counted() has_open = selected.is_open() has_adjusted = selected.has_adjusted_items() self.set_sensitive([self.PrintProductListing, self.Details], bool(selected)) self.set_sensitive([self.Cancel], has_open and not has_adjusted) self.set_sensitive([self.NewInventory], self._can_open()) self.set_sensitive([self.CountingAction], has_open and not all_counted) self.set_sensitive([self.AdjustAction], has_open and all_counted) self.window.set_new_menu_sensitive(self._can_open()) def _can_open(self): branch = api.get_current_branch(self.store) if Inventory.has_open(self.store, branch): return False # It doesn't make sense to open an inventory if we don't have any stock return self.store.find(ProductStockItem, branch=branch).count() > 0 def _open_inventory(self): with api.trans() as store: rv = self.run_dialog(InventoryOpenEditor, store) if rv: self.refresh() self._update_widgets() def _cancel_inventory(self): if not yesno(_('Are you sure you want to cancel this inventory ?'), gtk.RESPONSE_NO, _("Cancel inventory"), _("Don't cancel")): return store = api.new_store() inventory = store.fetch(self.results.get_selected()) inventory.cancel() store.commit() store.close() self.refresh() self._update_widgets() def _register_product_counting(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) model = self.run_dialog(InventoryCountWizard, store, inventory) store.confirm(model) store.close() self.refresh() self._update_widgets() def _adjust_product_quantities(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) model = self.run_dialog(InventoryAdjustmentEditor, store, inventory) store.confirm(model) store.close() self.refresh() self._update_widgets() def _update_filter_slave(self, slave): self.refresh() def _get_sellables_by_inventory(self, inventory): for item in inventory.get_items(): yield item.product.sellable def _show_inventory_details(self): store = api.new_store() inventory = store.fetch(self.results.get_selected()) self.run_dialog(InventoryDetailsDialog, store, inventory) store.close() # # Callbacks # def on_NewInventory__activate(self, action): self._open_inventory() def on_CountingAction__activate(self, action): self._register_product_counting() def on_AdjustAction__activate(self, action): self._adjust_product_quantities() def on_Details__activate(self, action): self._show_inventory_details() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_results__double_click(self, results, inventory): self._show_inventory_details() def on_Cancel__activate(self, widget): self._cancel_inventory() def on_PrintProductListing__activate(self, button): selected = self.results.get_selected() sellables = list(self._get_sellables_by_inventory(selected)) if not sellables: warning(_("No products found in the inventory.")) return self.print_report(ProductCountingReport, sellables)
class StockApp(ShellApp): app_title = _('Stock') gladefile = "stock" search_spec = ProductFullStockView search_labels = _('Matching:') report_table = SimpleProductReport pixbuf_converter = converter.get_converter(gtk.gdk.Pixbuf) # # Application # def create_actions(self): group = get_accels('app.stock') actions = [ ("NewReceiving", STOQ_RECEIVING, _("Order _receival..."), group.get('new_receiving')), ('NewTransfer', gtk.STOCK_CONVERT, _('Transfer...'), group.get('transfer_product')), ('NewStockDecrease', None, _('Stock decrease...'), group.get('stock_decrease')), ('StockInitial', gtk.STOCK_GO_UP, _('Register initial stock...')), ("LoanNew", None, _("Loan...")), ("LoanClose", None, _("Close loan...")), ("SearchPurchaseReceiving", None, _("Received purchases..."), group.get('search_receiving'), _("Search for received purchase orders")), ("SearchProductHistory", None, _("Product history..."), group.get('search_product_history'), _("Search for product history")), ("SearchStockDecrease", None, _("Stock decreases..."), '', _("Search for manual stock decreases")), ("SearchPurchasedStockItems", None, _("Purchased items..."), group.get('search_purchased_stock_items'), _("Search for purchased items")), ("SearchBrandItems", None, _("Brand items..."), group.get('search_brand_items'), _("Search for Brand items on stock")), ("SearchStockItems", None, _("Stock items..."), group.get('search_stock_items'), _("Search for items on stock")), ("SearchTransfer", None, _("Transfers..."), group.get('search_transfers'), _("Search for stock transfers")), ("SearchClosedStockItems", None, _("Closed stock Items..."), group.get('search_closed_stock_items'), _("Search for closed stock items")), ("LoanSearch", None, _("Loans...")), ("LoanSearchItems", None, _("Loan items...")), ("ProductMenu", None, _("Product")), ("ProductStockHistory", gtk.STOCK_INFO, _("History..."), group.get('history'), _('Show the stock history of the selected product')), ("EditProduct", gtk.STOCK_EDIT, _("Edit..."), group.get('edit_product'), _("Edit the selected product, allowing you to change it's " "details")), ] self.stock_ui = self.add_ui_actions('', actions, filename='stock.xml') toggle_actions = [ ('StockPictureViewer', None, _('Picture viewer'), group.get('toggle_picture_viewer')), ] self.add_ui_actions('', toggle_actions, 'ToggleActions', 'toggle') self.set_help_section(_("Stock help"), 'app-stock') self.NewReceiving.set_short_label(_("Receive")) self.NewTransfer.set_short_label(_("Transfer")) self.EditProduct.set_short_label(_("Edit")) self.ProductStockHistory.set_short_label(_("History")) self.EditProduct.props.is_important = True self.ProductStockHistory.props.is_important = True def create_ui(self): if api.sysparam(self.store).SMART_LIST_LOADING: self.search.enable_lazy_search() self.popup = self.uimanager.get_widget('/StockSelection') self.window.add_new_items([ self.NewReceiving, self.NewTransfer, self.NewStockDecrease, self.LoanNew ]) self.window.add_search_items([ self.SearchStockItems, self.SearchBrandItems, self.SearchStockDecrease, self.SearchClosedStockItems, self.SearchProductHistory, self.SearchPurchasedStockItems, self.SearchTransfer, ]) self.window.Print.set_tooltip(_("Print a report of these products")) self._inventory_widgets = [ self.NewTransfer, self.NewReceiving, self.StockInitial, self.NewStockDecrease, self.LoanNew, self.LoanClose ] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.image_viewer = None self.image = gtk.Image() self.edit_button = self.uimanager.get_widget( '/toolbar/AppToolbarPH/EditProduct') self.edit_button.set_icon_widget(self.image) self.image.show() self.search.set_summary_label(column='stock', label=_('<b>Stock Total:</b>'), format='<b>%s</b>', parent=self.get_statusbar_message_area()) def activate(self, refresh=True): self.window.NewToolItem.set_tooltip(_("Create a new receiving order")) self.window.SearchToolItem.set_tooltip(_("Search for stock items")) self.check_open_inventory() self._update_widgets() def setup_focus(self): self.refresh() def deactivate(self): self.uimanager.remove_ui(self.stock_ui) def new_activate(self): if not self.NewReceiving.get_sensitive(): warning(_("You cannot receive a purchase with an open inventory.")) return self._receive_purchase() def search_activate(self): self.run_dialog(ProductStockSearch, self.store) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.search.set_query(self._query) self.set_text_field_columns(['description']) self.branch_filter = ComboSearchFilter(_('Show by:'), self._get_branches()) self.branch_filter.select(api.get_current_branch(self.store)) self.add_filter(self.branch_filter, position=SearchFilterPosition.TOP) def get_columns(self): return [ SearchColumn('code', title=_('Code'), sorted=True, sort_func=sort_sellable_code, data_type=str, width=130), SearchColumn('barcode', title=_("Barcode"), data_type=str, width=130), SearchColumn('category_description', title=_("Category"), data_type=str, width=100, visible=False), SearchColumn('description', title=_("Description"), data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('manufacturer', title=_("Manufacturer"), data_type=str, visible=False), SearchColumn('model', title=_("Model"), data_type=str, visible=False), SearchColumn('location', title=_("Location"), data_type=str, width=100, visible=False), SearchColumn('stock', title=_('Quantity'), data_type=decimal.Decimal, width=100), SearchColumn('unit', title=_("Unit"), data_type=str, width=40, visible=False), Column('has_image', title=_('Picture'), data_type=bool, width=80), ] # # Private API # def _query(self, store): branch = self.branch_filter.get_state().value return self.search_spec.find_by_branch(store, branch) def _get_branches(self): items = [(b.person.name, b) for b in self.store.find(Branch)] if not items: raise DatabaseInconsistency('You should have at least one ' 'branch on your database.' 'Found zero') items.insert(0, [_('All branches'), None]) return items def _update_widgets(self): branch = api.get_current_branch(self.store) is_main_branch = self.branch_filter.get_state().value is branch item = self.results.get_selected() sellable = item and item.product.sellable if sellable: if sellable.has_image: thumbnail = sellable.image.thumbnail pixbuf = self.pixbuf_converter.from_string(thumbnail) else: pixbuf = None self._update_edit_image(pixbuf) if self.image_viewer: self.image_viewer.set_sellable(sellable) else: self._update_edit_image() self.set_sensitive([self.EditProduct], bool(item)) self.set_sensitive([self.ProductStockHistory], bool(item) and is_main_branch) # We need more than one branch to be able to do transfers # Note that 'all branches' is not a real branch has_branches = len(self.branch_filter.combo) > 2 transfer_active = self.NewTransfer.get_sensitive() self.set_sensitive([self.NewTransfer], transfer_active and has_branches) self.set_sensitive([self.SearchTransfer], has_branches) def _update_edit_image(self, pixbuf=None): if not pixbuf: self.image.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_LARGE_TOOLBAR) return # FIXME: get this icon size from settings icon_size = 24 pixbuf = pixbuf.scale_simple(icon_size, icon_size, gtk.gdk.INTERP_BILINEAR) self.image.set_from_pixbuf(pixbuf) def _update_filter_slave(self, slave): self.refresh() def _transfer_stock(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockTransferWizard, store) store.confirm(model) store.close() self.refresh() def _receive_purchase(self): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(ReceivingOrderWizard, store) store.confirm(model) store.close() self.refresh() # # Callbacks # def on_image_viewer_closed(self, window, event): self.StockPictureViewer.props.active = False self.image_viewer = None def on_results__has_rows(self, results, product): self._update_widgets() def on_results__selection_changed(self, results, product): self._update_widgets() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_ProductStockHistory__activate(self, button): selected = self.results.get_selected() sellable = selected.sellable self.run_dialog(ProductStockHistoryDialog, self.store, sellable, branch=self.branch_filter.combo.get_selected()) def on_EditProduct__activate(self, button): selected = self.results.get_selected() assert selected store = api.new_store() product = store.fetch(selected.product) model = self.run_dialog(ProductStockEditor, store, product) store.confirm(model) store.close() # Stock def on_NewReceiving__activate(self, button): self._receive_purchase() def on_NewTransfer__activate(self, button): self._transfer_stock() def on_NewStockDecrease__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(StockDecreaseWizard, store) store.confirm(model) store.close() self.refresh() def on_StockInitial__activate(self, action): if self.check_open_inventory(): return branch = self.branch_filter.get_state().value store = api.new_store() retval = self.run_dialog(InitialStockDialog, store, branch) store.confirm(retval) store.close() self.refresh() def on_StockPictureViewer__activate(self, button): if self.image_viewer: self.StockPictureViewer.props.active = False self.image_viewer.destroy() self.image_viewer = None else: self.StockPictureViewer.props.active = True self.image_viewer = SellableImageViewer() selected = self.results.get_selected() if selected: self.image_viewer.set_sellable(selected.product.sellable) self.image_viewer.toplevel.connect("delete-event", self.on_image_viewer_closed) self.image_viewer.toplevel.set_property("visible", True) # Loan def on_LoanNew__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(NewLoanWizard, store) store.confirm(model) store.close() self.refresh() def on_LoanClose__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(CloseLoanWizard, store) store.confirm(model) store.close() self.refresh() def on_LoanSearch__activate(self, action): self.run_dialog(LoanSearch, self.store) def on_LoanSearchItems__activate(self, action): self.run_dialog(LoanItemSearch, self.store) # Search def on_SearchPurchaseReceiving__activate(self, button): self.run_dialog(PurchaseReceivingSearch, self.store) def on_SearchTransfer__activate(self, action): self.run_dialog(TransferOrderSearch, self.store) self.refresh() def on_SearchPurchasedStockItems__activate(self, action): self.run_dialog(PurchasedItemsSearch, self.store) def on_SearchStockItems__activate(self, action): self.run_dialog(ProductStockSearch, self.store) def on_SearchBrandItems__activate(self, action): self.run_dialog(ProductBrandSearch, self.store) def on_SearchClosedStockItems__activate(self, action): self.run_dialog(ProductClosedStockSearch, self.store) def on_SearchProductHistory__activate(self, action): self.run_dialog(ProductSearchQuantity, self.store) def on_SearchStockDecrease__activate(self, action): self.run_dialog(StockDecreaseSearch, self.store)
class TransferOrderSearch(SearchDialog): title = _(u"Transfer Order Search") size = (750, 500) search_spec = TransferOrderView report_class = TransferOrderReport selection_mode = gtk.SELECTION_MULTIPLE def __init__(self, store): SearchDialog.__init__(self, store) self._setup_widgets() def _show_transfer_order_details(self, order_view): transfer_order = order_view.transfer_order run_dialog(TransferOrderDetailsDialog, self, self.store, transfer_order) def _setup_widgets(self): self.results.connect('row_activated', self.on_row_activated) self.update_widgets() def _get_status_values(self): items = [(str(value), key) for key, value in TransferOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items # # SearchDialog Hooks # def update_widgets(self): orders = self.results.get_selected_rows() has_one_selected = len(orders) == 1 self.set_details_button_sensitive(has_one_selected) self.set_print_button_sensitive(has_one_selected) def create_filters(self): self.set_text_field_columns(['source_branch_name', 'destination_branch_name', 'identifier_str']) # Date self.date_filter = DateSearchFilter(_('Date:')) self.add_filter(self.date_filter, columns=['open_date', 'receival_date']) # Status self.status_filter = ComboSearchFilter(_('With status:'), self._get_status_options()) self.status_filter.select('pending') executer = self.search.get_query_executer() executer.add_filter_query_callback(self.status_filter, self._get_status_query) self.add_filter(self.status_filter, position=SearchFilterPosition.TOP) def _get_status_options(self): return [ (_('All transfers'), None), (_('Pending receive'), 'pending'), (_('Received'), 'received'), (_('Sent'), 'sent'), ] def _get_status_query(self, state): current_branch = api.get_current_branch(self.store) if state.value == 'pending': return And(TransferOrder.status == TransferOrder.STATUS_SENT, TransferOrder.destination_branch_id == current_branch.id) elif state.value == 'received': return And(TransferOrder.status == TransferOrder.STATUS_RECEIVED, TransferOrder.destination_branch_id == current_branch.id) elif state.value == 'sent': return And(TransferOrder.source_branch_id == current_branch.id) else: return Or(TransferOrder.source_branch_id == current_branch.id, TransferOrder.destination_branch_id == current_branch.id) def get_columns(self): return [IdentifierColumn('identifier'), SearchColumn('transfer_order.status_str', _('Status'), data_type=str, valid_values=self._get_status_values(), search_attribute='status', width=100), SearchColumn('open_date', _('Open date'), data_type=datetime.date, sorted=True, width=100), SearchColumn('receival_date', _('Receival Date'), data_type=datetime.date, width=100, visible=False), SearchColumn('source_branch_name', _('Source'), data_type=str, expand=True), SearchColumn('destination_branch_name', _('Destination'), data_type=str, width=220), Column('total_items', _('Items'), data_type=Decimal, format_func=format_quantity, width=110)] # # Callbacks # def on_row_activated(self, klist, view): self._show_transfer_order_details(view) def on_details_button_clicked(self, button): self._show_transfer_order_details(self.results.get_selected_rows()[0])