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_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 SellableSearch(SearchEditor): title = _('Item search') size = (800, 500) model_list_lookup_attr = 'product_id' footer_ok_label = _('_Select item') search_spec = SellableFullStockView editor_class = None exclude_delivery_service = True text_field_columns = [ SellableFullStockView.description, SellableFullStockView.category_description, SellableFullStockView.barcode, SellableFullStockView.code ] def __init__(self, store, hide_footer=False, hide_toolbar=True, selection_mode=None, search_str=None, search_spec=None, search_query=None, double_click_confirm=True, info_message=None, show_closed_items=False): """ :param store: a store :param hide_footer: do I have to hide the dialog footer? :param hide_toolbar: do I have to hide the dialog toolbar? :param selection_mode: the kiwi list selection mode :param search_str: If this search should already filter for some string :param double_click_confirm: If double click a item in the list should automatically confirm :param show_closed_items: if this parameter is True, shows sellable with status closed """ if selection_mode is None: selection_mode = Gtk.SelectionMode.BROWSE self._image_viewer = None self._first_search = True self._first_search_string = search_str self._search_query = search_query self._show_closed_items = show_closed_items self._delivery_sellable = sysparam.get_object( store, 'DELIVERY_SERVICE').sellable SearchEditor.__init__(self, store, search_spec=search_spec, editor_class=self.editor_class, hide_footer=hide_footer, hide_toolbar=hide_toolbar, selection_mode=selection_mode, double_click_confirm=double_click_confirm) if info_message: self.set_message(info_message) if search_str: self.set_searchbar_search_string(search_str) self.search.refresh() # # SearchEditor # def key_shift_Return(self): self.confirm() def key_control_Return(self): self.confirm() def key_shift_KP_Enter(self): self.confirm() def key_control_KP_Enter(self): self.confirm() def close(self): # Make sure image viewer gets closed when this search closes self._close_image_viewer() super(SellableSearch, self).close() def confirm(self, retval=None): # FIXME: This is a hack, we need to do proper validation in the parent if retval is None and not self.ok_button.get_sensitive(): return super(SellableSearch, self).confirm(retval=retval) def setup_widgets(self): self.image_viewer_toggler = Gtk.CheckMenuItem( label=_("Show image viewer")) self.popup = Gtk.Menu() self.popup.add(self.image_viewer_toggler) self.popup.show_all() if hasattr(self.search_spec, 'product'): self.branch_stock_button = self.add_button( label=_('Stock details')) self.branch_stock_button.show() self.branch_stock_button.set_sensitive(False) else: self.branch_stock_button = None def create_filters(self): self.search.set_query(self.executer_query) def get_columns(self): columns = [ SearchColumn('code', title=_(u'Code'), data_type=str), SearchColumn('barcode', title=_('Barcode'), data_type=str, sort_func=sort_sellable_code, width=80), SearchColumn('category_description', title=_('Category'), data_type=str, width=120), SearchColumn('description', title=_('Description'), data_type=str, expand=True, sorted=True), SearchColumn('location', title=_('Location'), data_type=str, visible=False), SearchColumn('manufacturer', title=_('Manufacturer'), data_type=str, visible=False), SearchColumn('model', title=_('Model'), data_type=str, visible=False) ] if hasattr(self.search_spec, 'price'): columns.append( SearchColumn('price', title=_(u'Price'), data_type=currency, visible=True)) if hasattr(self.search_spec, 'minimum_quantity'): columns.append( SearchColumn('minimum_quantity', title=_(u'Minimum Qty'), data_type=Decimal, visible=False)) if hasattr(self.search_spec, 'stock'): columns.append(QuantityColumn('stock', title=_(u'In Stock'))) return columns def update_widgets(self): sellable_view = self.results.get_selected() self.set_edit_button_sensitive(bool(sellable_view)) self.ok_button.set_sensitive(bool(sellable_view)) if self.branch_stock_button is not None: self.branch_stock_button.set_sensitive( bool(self._get_selected_storable())) def executer_query(self, store): # If the viewable has a find_by_branch method, then lets use it instead # of the generic find, to show only the stock for the current branch. if hasattr(self.search_spec, 'find_by_branch'): branch = api.get_current_branch(store) results = self.search_spec.find_by_branch(store, branch) else: results = store.find(self.search_spec) if self._search_query: results = results.find(self._search_query) if not self._show_closed_items: results = results.find( self.search_spec.status == Sellable.STATUS_AVAILABLE) if self.exclude_delivery_service: results = results.find( self.search_spec.id != self._delivery_sellable.id) return results # # Private # def _get_selected_storable(self): product = getattr(self.results.get_selected(), 'product', None) if product and self.fast_iter: product = self.store.get(Product, product.id) return product and product.storable def _open_image_viewer(self): assert self._image_viewer is None self._image_viewer = SellableImageViewer(size=(325, 325)) self._update_image_viewer() self._image_viewer.toplevel.connect( 'delete-event', self._on_image_viewer__delete_event) self._image_viewer.show_all() def _close_image_viewer(self): if self._image_viewer is None: return self._image_viewer.destroy() self._image_viewer = None def _update_image_viewer(self): if self._image_viewer is None: return row = self.results.get_selected() self._image_viewer.set_sellable(row and row.sellable) # # Callbacks # def _on_image_viewer__delete_event(self, window, event): self._image_viewer = None self.image_viewer_toggler.set_active(False) def on_image_viewer_toggler__toggled(self, item): if item.get_active(): self._open_image_viewer() else: self._close_image_viewer() def on_results__right_click(self, klist, row, event): self.popup.popup(None, None, None, None, event.button.button, event.time) def on_results__selection_changed(self, klist, row): self._update_image_viewer() def on_branch_stock_button__clicked(self, widget): from stoqlib.gui.search.productsearch import ProductBranchSearch storable = self._get_selected_storable() if not storable: return self.run_dialog(ProductBranchSearch, self, self.store, storable)
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 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 SellableSearch(SearchEditor): title = _('Item search') size = (800, 500) model_list_lookup_attr = 'product_id' footer_ok_label = _('_Select item') search_spec = SellableFullStockView editor_class = None exclude_delivery_service = True text_field_columns = [SellableFullStockView.description, SellableFullStockView.category_description, SellableFullStockView.barcode, SellableFullStockView.code] def __init__(self, store, hide_footer=False, hide_toolbar=True, selection_mode=None, search_str=None, search_spec=None, search_query=None, double_click_confirm=True, info_message=None): """ :param store: a store :param hide_footer: do I have to hide the dialog footer? :param hide_toolbar: do I have to hide the dialog toolbar? :param selection_mode: the kiwi list selection mode :param search_str: If this search should already filter for some string :param double_click_confirm: If double click a item in the list should automatically confirm """ if selection_mode is None: selection_mode = gtk.SELECTION_BROWSE self._image_viewer = None self._first_search = True self._first_search_string = search_str self._search_query = search_query self._delivery_sellable = sysparam.get_object( store, 'DELIVERY_SERVICE').sellable SearchEditor.__init__(self, store, search_spec=search_spec, editor_class=self.editor_class, hide_footer=hide_footer, hide_toolbar=hide_toolbar, selection_mode=selection_mode, double_click_confirm=double_click_confirm) if info_message: self.set_message(info_message) if search_str: self.set_searchbar_search_string(search_str) self.search.refresh() # # SearchEditor # def key_shift_Return(self): self.confirm() def key_control_Return(self): self.confirm() def key_shift_KP_Enter(self): self.confirm() def key_control_KP_Enter(self): self.confirm() def close(self): # Make sure image viewer gets closed when this search closes self._close_image_viewer() super(SellableSearch, self).close() def confirm(self, retval=None): # FIXME: This is a hack, we need to do proper validation in the parent if retval is None and not self.ok_button.get_sensitive(): return super(SellableSearch, self).confirm(retval=retval) def setup_widgets(self): self.image_viewer_toggler = gtk.CheckMenuItem(_("Show image viewer")) self.popup = gtk.Menu() self.popup.add(self.image_viewer_toggler) self.popup.show_all() if hasattr(self.search_spec, 'product'): self.branch_stock_button = self.add_button(label=_('Stock details')) self.branch_stock_button.show() self.branch_stock_button.set_sensitive(False) else: self.branch_stock_button = None def create_filters(self): self.search.set_query(self.executer_query) def get_columns(self): columns = [SearchColumn('code', title=_(u'Code'), data_type=str), SearchColumn('barcode', title=_('Barcode'), data_type=str, sort_func=sort_sellable_code, width=80), SearchColumn('category_description', title=_('Category'), data_type=str, width=120), SearchColumn('description', title=_('Description'), data_type=str, expand=True, sorted=True), SearchColumn('manufacturer', title=_('Manufacturer'), data_type=str, visible=False), SearchColumn('model', title=_('Model'), data_type=str, visible=False)] if hasattr(self.search_spec, 'price'): columns.append(SearchColumn('price', title=_(u'Price'), data_type=currency, visible=True)) if hasattr(self.search_spec, 'minimum_quantity'): columns.append(SearchColumn('minimum_quantity', title=_(u'Minimum Qty'), data_type=Decimal, visible=False)) if hasattr(self.search_spec, 'stock'): columns.append(SearchColumn('stock', title=_(u'In Stock'), data_type=Decimal)) return columns def update_widgets(self): sellable_view = self.results.get_selected() self.set_edit_button_sensitive(bool(sellable_view)) self.ok_button.set_sensitive(bool(sellable_view)) # Some viewables may not have the product (for viewables with only # services). Also use hasattr for product since it may be None if self.branch_stock_button is not None: storable = (getattr(sellable_view, 'product', None) and getattr(sellable_view.product, 'storable')) self.branch_stock_button.set_sensitive(bool(storable)) def executer_query(self, store): # If the viewable has a find_by_branch method, then lets use it instead # of the generic find, to show only the stock for the current branch. if hasattr(self.search_spec, 'find_by_branch'): branch = api.get_current_branch(store) results = self.search_spec.find_by_branch(store, branch) else: results = store.find(self.search_spec) if self._search_query: results = results.find(self._search_query) if self.exclude_delivery_service: results = results.find(And( self.search_spec.status == Sellable.STATUS_AVAILABLE, self.search_spec.id != self._delivery_sellable.id)) return results # # Private # def _open_image_viewer(self): assert self._image_viewer is None self._image_viewer = SellableImageViewer(size=(325, 325)) self._update_image_viewer() self._image_viewer.toplevel.connect( 'delete-event', self._on_image_viewer__delete_event) self._image_viewer.show_all() def _close_image_viewer(self): if self._image_viewer is None: return self._image_viewer.destroy() self._image_viewer = None def _update_image_viewer(self): if self._image_viewer is None: return row = self.results.get_selected() self._image_viewer.set_sellable(row.sellable) # # Callbacks # def _on_image_viewer__delete_event(self, window, event): self._image_viewer = None self.image_viewer_toggler.set_active(False) def on_image_viewer_toggler__toggled(self, item): if item.get_active(): self._open_image_viewer() else: self._close_image_viewer() def on_results__right_click(self, klist, row, event): self.popup.popup(None, None, None, event.button, event.time) def on_results__selection_changed(self, klist, row): self._update_image_viewer() def on_branch_stock_button__clicked(self, widget): from stoqlib.gui.search.productsearch import ProductBranchSearch viewable = self.results.get_selected() product = viewable.product if product and product.storable: self.run_dialog(ProductBranchSearch, self, self.store, product.storable)
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", None, _("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) toggle_actions = [ ('StockPictureViewer', None, _('Picture viewer'), group.get('toggle_picture_viewer')), ] self.add_ui_actions(toggle_actions, 'ToggleActions') self.set_help_section(_("Stock help"), 'app-stock') def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.window.add_print_items([self.PrintLabels]) self.window.add_export_items() self.window.add_extra_items([self.StockInitial, self.LoanClose, self.StockPictureViewer]) self.window.add_new_items([self.NewReceiving, self.NewTransfer, self.NewStockDecrease, self.LoanNew]) self.window.add_search_items([ self.SearchPurchaseReceiving, self.SearchProductHistory, self.SearchTransfer, self.SearchTransferItems, self.SearchStockDecrease, self.SearchReturnedItems, self.SearchPurchasedStockItems, self.SearchStockItems, self.SearchBrandItems, self.SearchBrandItemsByBranch, self.SearchBatchItems, self.SearchClosedStockItems, self.SearchPendingReturnedSales, # Separator self.LoanSearch, self.LoanSearchItems, ]) 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() # FIXME: How are we goint to display an image preview? #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 get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('History'), 'stock.ProductStockHistory', True), ('fa-edit-symbolic', _('Edit'), 'stock.EditProduct', True), ('', _('Print labels'), 'stock.PrintLabels', False), ('', _('Manage stock'), 'stock.ManageStock', False), ] return options def activate(self, refresh=True): 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._close_image_viewer() 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_enabled() 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_state(GLib.Variant.new_boolean(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_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__change_state(self, action, value): action.set_state(value) if value.get_boolean(): 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)