class ProductionApp(ShellApp): app_title = _(u'Production') gladefile = "production" search_spec = ProductionOrder search_label = _(u'matching:') report_table = ProductionReport # # Application # def create_actions(self): group = get_accels('app.production') actions = [ ('menubar', None, ''), # File ('NewProduction', Gtk.STOCK_NEW, _('Production order...'), group.get('new_production_order'), _('Create a new production')), ('ProductionPurchaseQuote', None, _('Purchase quote...'), group.get('new_production_quote')), # Production ('ProductionMenu', None, _('Production')), ('StartProduction', Gtk.STOCK_CONVERT, _('Start production...'), group.get('production_start'), _('Start the selected production')), ('EditProduction', Gtk.STOCK_EDIT, _('Edit production...'), group.get('production_edit'), _('Edit the selected production')), ('FinalizeProduction', Gtk.STOCK_APPLY, _('Finalize production...'), None, _('Finalize the selected production')), ('CancelProduction', Gtk.STOCK_CANCEL, _('Cancel production...'), None, _('Cancel the selected production')), ('ProductionDetails', Gtk.STOCK_INFO, _('Production details...'), group.get('production_details'), _('Show production details and register produced items')), # Search ("SearchProduct", None, _("Production products..."), group.get('search_production_products'), _("Search for production products")), ("SearchService", None, _("Services..."), group.get('search_services'), _("Search for services")), ("SearchProductionItem", None, _("Production items..."), group.get('search_production_items'), _("Search for production items")), ("SearchProductionHistory", None, _("Production history..."), group.get('search_production_history'), _("Search for production history")), ] self.production_ui = self.add_ui_actions(actions) self.set_help_section(_("Production help"), 'app-production') def create_ui(self): self.window.add_new_items( [self.NewProduction, self.ProductionPurchaseQuote]) self.window.add_search_items([ self.SearchProduct, self.SearchService, self.SearchProductionItem, self.SearchProductionHistory, ]) self._inventory_widgets = [self.StartProduction] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'production.ProductionDetails', True), ('fa-edit-symbolic', _('Edit production'), 'production.EditProduction', True), ('fa-play-symbolic', _('Start production'), 'production.StartProduction', True), ('fa-stop-symbolic', _('Finalize production'), 'production.FinalizeProduction', True), ('fa-ban-symbolic', _('Cancel production'), 'production.CancelProduction', True), ] return options def activate(self, refresh=True): if refresh: self.refresh() self._update_widgets() self.check_open_inventory() self.search.focus_search_entry() def create_filters(self): self.set_text_field_columns(['description']) self.status_filter = ComboSearchFilter( _(u'Show productions with status'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) def get_columns(self): return [ IdentifierColumn('identifier', title=_('Production #'), sorted=True, order=Gtk.SortType.DESCENDING), Column('status_string', title=_(u'Status'), data_type=str, visible=False), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), Column('responsible.person.name', title=_(u'Responsible'), data_type=str, width=150), SearchColumn('open_date', title=_(u'Opened'), data_type=datetime.date, width=80), SearchColumn('close_date', title=_(u'Closed'), data_type=datetime.date, width=80), SearchColumn('cancel_date', title=_(u'Cancelled'), data_type=datetime.date, width=80) ] def print_report(self, *args, **kwargs): # ProductionReport needs a status kwarg kwargs['status'] = self.status_filter.get_state().value super(ProductionApp, self).print_report(*args, **kwargs) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) # # Private # def _update_widgets(self): selected = self.results.get_selected() can_edit = False can_start = False can_finalize = False can_cancel = False if selected: can_edit = (selected.status == ProductionOrder.ORDER_OPENED or selected.status == ProductionOrder.ORDER_WAITING) can_start = can_edit can_finalize = (selected.status == ProductionOrder.ORDER_PRODUCING) can_cancel = can_edit self.set_sensitive([self.EditProduction], can_edit) self.set_sensitive([self.StartProduction], can_start) self.set_sensitive([self.FinalizeProduction], can_finalize) self.set_sensitive([self.CancelProduction], can_cancel) self.set_sensitive([self.ProductionDetails], bool(selected)) def _get_status_values(self): items = [(text, value) for value, text in ProductionOrder.statuses.items()] items.insert(0, (_(u'Any'), None)) return items def _open_production_order(self, order=None): store = api.new_store() order = store.fetch(order) retval = self.run_dialog(ProductionWizard, store, order) store.confirm(retval) store.close() self.refresh() def _start_production_order(self): store = api.new_store() order = store.fetch(self.results.get_selected()) assert order is not None retval = self.run_dialog(StartProductionDialog, store, order) store.confirm(retval) store.close() self.refresh() def _production_details(self): order = self.results.get_selected() assert order is not None store = api.new_store() model = store.fetch(order) initial_status = model.status self.run_dialog(ProductionDetailsDialog, store, model) store.confirm(True) if initial_status != model.status: self.refresh() store.close() def _finalize_production(self): if not yesno(_("The selected order will be finalized."), Gtk.ResponseType.YES, _("Finalize order"), _("Don't finalize")): return with api.new_store() as store: model = store.fetch(self.results.get_selected()) model.try_finalize_production(ignore_completion=True) self.refresh() def _cancel_production(self): if not yesno(_("The selected order will be cancelled."), Gtk.ResponseType.YES, _("Cancel order"), _("Don't cancel")): return with api.new_store() as store: order = self.results.get_selected() model = store.fetch(order) model.cancel() self.refresh() # # Kiwi Callbacks # def on_EditProduction__activate(self, widget): order = self.results.get_selected() assert order is not None self._open_production_order(order) def on_ProductionDetails__activate(self, widget): self._production_details() def on_FinalizeProduction__activate(self, widget): self._finalize_production() def on_CancelProduction__activate(self, widget): self._cancel_production() def on_results__selection_changed(self, results, selected): self._update_widgets() def on_results__has_rows(self, widget, has_rows): self._update_widgets() def on_results__row_activated(self, widget, order): self._production_details() # Production def on_NewProduction__activate(self, action): self._open_production_order() def on_StartProduction__activate(self, action): self._start_production_order() def on_ProductionPurchaseQuote__activate(self, action): with api.new_store() as store: self.run_dialog(ProductionQuoteDialog, store) # Search def on_SearchProduct__activate(self, action): self.run_dialog(ProductionProductSearch, self.store) def on_SearchService__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_SearchProductionItem__activate(self, action): self.run_dialog(ProductionItemsSearch, self.store) def on_SearchProductionHistory__activate(self, action): self.run_dialog(ProductionHistorySearch, self.store)
class PurchaseApp(ShellApp): # TODO: Change all widget.set_sensitive to self.set_sensitive([widget]) app_title = _('Purchase') gladefile = "purchase" search_spec = PurchaseOrderView search_label = _('matching:') report_table = PurchaseReport action_permissions = { 'ProductUnits': ('ProductUnit', PermissionManager.PERM_SEARCH), 'NewProduct': ('Product', PermissionManager.PERM_CREATE), 'Products': ('Product', PermissionManager.PERM_SEARCH), 'Services': ('Service', PermissionManager.PERM_SEARCH), } # # Application # def create_actions(self): group = get_accels('app.purchase') actions = [ # File ("OrderMenu", None, _("Order")), ("NewOrder", gtk.STOCK_NEW, _("Order..."), group.get('new_order'), _("Create a new purchase order")), ("NewQuote", gtk.STOCK_INDEX, _("Quote..."), group.get('new_quote'), _("Create a new purchase quote")), ("NewConsignment", None, _("Consignment..."), group.get('new_consignment'), _("Create a new purchase consignment")), ("NewProduct", None, _("Product..."), group.get('new_product'), _("Create a new product")), ("CloseInConsignment", None, _("Close consigment...")), # Edit ("StockCost", None, _("_Stock cost...")), # Search ("Categories", None, _("Categories..."), group.get("search_categories")), ("Products", STOQ_PRODUCTS, _("Products..."), group.get("search_products")), ("ProductUnits", None, _("Product units..."), group.get("search_product_units")), ("ProductManufacturers", None, _("Manufacturers..."), group.get("search_product_manufacturers")), ("Services", None, _("Services..."), group.get("search_services")), ("SearchStockItems", None, _("Stock items..."), group.get("search_stock_items")), ("SearchClosedStockItems", None, _("Closed stock items..."), group.get("search_closed_stock_items")), ("Suppliers", STOQ_SUPPLIERS, _("Suppliers..."), group.get("search_suppliers")), ("Transporter", None, _("Transporters..."), group.get("search_transporters")), ("SearchQuotes", None, _("Quotes..."), group.get("search_quotes")), ("SearchPurchasedItems", None, _("Purchased items..."), group.get("search_purchased_items")), ("ProductsSoldSearch", None, _("Sold products..."), group.get("search_products_sold")), ("ProductsPriceSearch", None, _("Prices..."), group.get("search_prices")), ("SearchInConsignmentItems", None, _("Search consigment items..."), group.get("search_consignment_items")), # Order ("Confirm", gtk.STOCK_APPLY, _("Confirm..."), group.get('order_confirm'), _("Confirm the selected order(s), marking it as sent to the " "supplier")), ("Cancel", gtk.STOCK_CANCEL, _("Cancel..."), group.get('order_cancel'), _("Cancel the selected order")), ("Edit", gtk.STOCK_EDIT, _("Edit..."), group.get('order_edit'), _("Edit the selected order, allowing you to change it's details") ), ("Details", gtk.STOCK_INFO, _("Details..."), group.get('order_details'), _("Show details of the selected order")), ("Finish", gtk.STOCK_APPLY, _("Finish..."), group.get('order_finish'), _('Complete the selected partially received order')), ] self.purchase_ui = self.add_ui_actions("", actions, filename="purchase.xml") self.Confirm.props.is_important = True self.NewOrder.set_short_label(_("New order")) self.NewQuote.set_short_label(_("New quote")) self.Products.set_short_label(_("Products")) self.Suppliers.set_short_label(_("Suppliers")) self.Confirm.set_short_label(_("Confirm")) self.Cancel.set_short_label(_("Cancel")) self.Finish.set_short_label(_("Finish")) self.Edit.set_short_label(_("Edit")) self.Details.set_short_label(_("Details")) self.set_help_section(_("Purchase help"), 'app-purchase') self.popup = self.uimanager.get_widget('/PurchaseSelection') def create_ui(self): if api.sysparam(self.store).SMART_LIST_LOADING: self.search.enable_lazy_search() self.window.add_new_items([ self.NewOrder, self.NewQuote, self.NewProduct, self.NewConsignment ]) self.window.add_search_items( [self.Products, self.Suppliers, self.SearchQuotes, self.Services]) self.search.set_summary_label(column='total', label=('<b>%s</b>' % api.escape(_('Orders total:'))), format='<b>%s</b>', parent=self.get_statusbar_message_area()) self.results.set_selection_mode(gtk.SELECTION_MULTIPLE) self.Confirm.set_sensitive(False) self._inventory_widgets = [ self.NewConsignment, self.CloseInConsignment ] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def activate(self, refresh=True): self.window.NewToolItem.set_tooltip(_("Create a new purchase order")) self.window.SearchToolItem.set_tooltip(_("Search for purchase orders")) self.window.Print.set_tooltip(_("Print a report of these orders")) if refresh: self._update_view() self.results.set_selection_mode(gtk.SELECTION_MULTIPLE) self.check_open_inventory() def setup_focus(self): self.refresh() def deactivate(self): self.uimanager.remove_ui(self.purchase_ui) def new_activate(self): self._new_order() def search_activate(self): self.run_dialog(ProductSearch, self.store, hide_price_column=True) def search_completed(self, results, states): if len(results): return supplier, status = states[:2] if len(states) > 2 or (supplier.text == '' and status.value is None): self.search.set_message( "%s\n\n%s" % (_("No orders could be found."), _("Would you like to %s ?") % ('<a href="new_order">%s</a>' % (api.escape(_("create a new order"), ))))) # FIXME: Push number of results to Statusbar def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.set_text_field_columns(['supplier_name', 'identifier_str']) self.status_filter = ComboSearchFilter(_('Show orders'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) def get_columns(self): return [ IdentifierColumn('identifier', long_title=_('Order #')), SearchColumn('status_str', title=_('Status'), width=100, data_type=str, search_attribute='status', valid_values=self._get_status_values(), visible=False), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), width=90, data_type=datetime.date, sorted=True, order=gtk.SORT_DESCENDING), SearchColumn('supplier_name', title=_('Supplier'), data_type=str, searchable=True, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('ordered_quantity', title=_('Ordered'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('received_quantity', title=_('Received'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('total', title=_('Total'), data_type=currency, width=120) ] def print_report(self, *args, **kwargs): # PurchaseReport needs a status arg kwargs['status'] = self.status_filter.get_state().value super(PurchaseApp, self).print_report(*args, **kwargs) def search_for_date(self, date): dfilter = DateSearchFilter(_("Expected receival date")) dfilter.set_removable() dfilter.mode.select_item_by_position(5) self.add_filter(dfilter, columns=["expected_receival_date"]) dfilter.start_date.set_date(date) self.refresh() # # Private # def _update_totals(self): self._update_view() def _update_list_aware_widgets(self, has_items): for widget in (self.Edit, self.Details): widget.set_sensitive(has_items) def _update_view(self): self._update_list_aware_widgets(len(self.results)) selection = self.results.get_selected_rows() can_edit = one_selected = len(selection) == 1 can_finish = False if selection: can_send_supplier = all(order.status == PurchaseOrder.ORDER_PENDING for order in selection) can_cancel = all(order_view.purchase.can_cancel() for order_view in selection) else: can_send_supplier = False can_cancel = False if one_selected: can_edit = (selection[0].status == PurchaseOrder.ORDER_PENDING or selection[0].status == PurchaseOrder.ORDER_QUOTING) can_finish = (selection[0].status == PurchaseOrder.ORDER_CONFIRMED and selection[0].received_quantity > 0) self.Cancel.set_sensitive(can_cancel) self.Edit.set_sensitive(can_edit) self.Confirm.set_sensitive(can_send_supplier) self.Details.set_sensitive(one_selected) self.Finish.set_sensitive(can_finish) def _new_order(self, order=None, edit_mode=False): with api.trans() as store: order = store.fetch(order) self.run_dialog(PurchaseWizard, store, order, edit_mode) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) def _edit_order(self): selected = self.results.get_selected_rows() qty = len(selected) if qty != 1: raise ValueError('You should have only one order selected, ' 'got %d instead' % qty) purchase = selected[0].purchase if purchase.status == PurchaseOrder.ORDER_PENDING: self._new_order(purchase, edit_mode=False) else: self._quote_order(purchase) def _run_details_dialog(self): order_views = self.results.get_selected_rows() qty = len(order_views) if qty != 1: raise ValueError('You should have only one order selected ' 'at this point, got %d' % qty) self.run_dialog(PurchaseDetailsDialog, self.store, model=order_views[0].purchase) def _send_selected_items_to_supplier(self): orders = self.results.get_selected_rows() valid_order_views = [ order for order in orders if order.status == PurchaseOrder.ORDER_PENDING ] if not valid_order_views: warning(_("There are no pending orders selected.")) return msg = stoqlib_ngettext( _("The selected order will be marked as sent."), _("The %d selected orders will be marked as sent.") % len(valid_order_views), len(valid_order_views)) confirm_label = stoqlib_ngettext(_("Confirm order"), _("Confirm orders"), len(valid_order_views)) if not yesno(msg, gtk.RESPONSE_YES, confirm_label, _("Don't confirm")): return with api.trans() as store: for order_view in valid_order_views: order = store.fetch(order_view.purchase) order.confirm() self.refresh() self.select_result(orders) def _finish_order(self): order_views = self.results.get_selected_rows() qty = len(order_views) if qty != 1: raise ValueError('You should have only one order selected ' 'at this point, got %d' % qty) with api.trans() as store: order = store.fetch(order_views[0].purchase) self.run_dialog(PurchaseFinishWizard, store, order) self.refresh() self.select_result(order_views) def _cancel_order(self): order_views = self.results.get_selected_rows() assert all(ov.purchase.can_cancel() for ov in order_views) cancel_label = stoqlib_ngettext(_("Cancel order"), _("Cancel orders"), len(order_views)) select_label = stoqlib_ngettext( _('The selected order will be cancelled.'), _('The selected orders will be cancelled.'), len(order_views)) if not yesno(select_label, gtk.RESPONSE_YES, cancel_label, _("Don't cancel")): return with api.trans() as store: for order_view in order_views: order = store.fetch(order_view.purchase) order.cancel() self._update_totals() self.refresh() self.select_result(order_views) def _get_status_values(self): items = [(text, value) for value, text in PurchaseOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items def _quote_order(self, quote=None): with api.trans() as store: quote = store.fetch(quote) self.run_dialog(QuotePurchaseWizard, store, quote) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) def _new_product(self): with api.trans() as store: self.run_dialog(ProductEditor, store, model=None) def _new_consignment(self): with api.trans() as store: self.run_dialog(ConsignmentWizard, store, model=None) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) # # Kiwi Callbacks # def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_results__row_activated(self, klist, purchase_order_view): self._run_details_dialog() def on_results__selection_changed(self, results, selected): self._update_view() def on_results__activate_link(self, results, uri): if uri == 'new_order': self._new_order() def _on_results__double_click(self, results, order): self._run_details_dialog() def _on_results__has_rows(self, results, has_items): self._update_list_aware_widgets(has_items) def on_Details__activate(self, action): self._run_details_dialog() def on_Edit__activate(self, action): self._edit_order() def on_Cancel__activate(self, action): self._cancel_order() def on_Confirm__activate(self, button): self._send_selected_items_to_supplier() def on_Finish__activate(self, action): self._finish_order() # Order def on_StockCost__activate(self, action): self.run_dialog(StockCostDialog, self.store, None) # Consignment def on_CloseInConsignment__activate(self, action): with api.trans() as store: self.run_dialog(CloseInConsignmentWizard, store) def on_SearchInConsignmentItems__activate(self, action): self.run_dialog(ConsignmentItemSearch, self.store) def on_Categories__activate(self, action): self.run_dialog(SellableCategorySearch, self.store) def on_SearchQuotes__activate(self, action): with api.trans() as store: self.run_dialog(ReceiveQuoteWizard, store) self.refresh() def on_SearchPurchasedItems__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_Suppliers__activate(self, action): self.run_dialog(SupplierSearch, self.store, hide_footer=True) def on_Products__activate(self, action): self.run_dialog(ProductSearch, self.store, hide_price_column=True) def on_ProductUnits__activate(self, action): self.run_dialog(SellableUnitSearch, self.store) def on_ProductManufacturers__activate(self, action): self.run_dialog(ProductManufacturerDialog, self.store) def on_Services__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_Transporter__activate(self, action): self.run_dialog(TransporterSearch, self.store, hide_footer=True) def on_ProductsSoldSearch__activate(self, action): self.run_dialog(ProductsSoldSearch, self.store) def on_ProductsPriceSearch__activate(self, action): from stoqlib.domain.person import ClientCategory if not self.store.find(ClientCategory).count(): warning(_("Can't use prices editor without client categories")) return with api.trans() as store: self.run_dialog(SellablePriceDialog, store) # Toolitem def on_NewOrder__activate(self, action): self._new_order() def on_NewQuote__activate(self, action): self._quote_order() def on_NewProduct__activate(self, action): self._new_product() def on_NewConsignment__activate(self, action): self._new_consignment()
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 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 PurchaseApp(ShellApp): # TODO: Change all widget.set_sensitive to self.set_sensitive([widget]) app_title = _('Purchase') gladefile = "purchase" search_spec = PurchaseOrderView search_label = _('matching:') report_table = PurchaseReport action_permissions = { 'ProductUnits': ('ProductUnit', PermissionManager.PERM_SEARCH), 'NewProduct': ('Product', PermissionManager.PERM_CREATE), 'Products': ('Product', PermissionManager.PERM_SEARCH), 'Services': ('Service', PermissionManager.PERM_SEARCH), } # # Application # def create_actions(self): group = get_accels('app.purchase') actions = [ # File ("OrderMenu", None, _("Order")), ("NewOrder", gtk.STOCK_NEW, _("Order..."), group.get('new_order'), _("Create a new purchase order")), ("NewQuote", gtk.STOCK_INDEX, _("Quote..."), group.get('new_quote'), _("Create a new purchase quote")), ("NewConsignment", None, _("Consignment..."), group.get('new_consignment'), _("Create a new purchase consignment")), ("NewProduct", None, _("Product..."), group.get('new_product'), _("Create a new product")), ("CloseInConsignment", None, _("Close consigment...")), # Edit ("StockCost", None, _("_Stock cost...")), # Search ("Categories", None, _("Categories..."), group.get("search_categories")), ("Products", STOQ_PRODUCTS, _("Products..."), group.get("search_products")), ("ProductUnits", None, _("Product units..."), group.get("search_product_units")), ("ProductManufacturers", None, _("Manufacturers..."), group.get("search_product_manufacturers")), ("Services", None, _("Services..."), group.get("search_services")), ("SearchStockItems", None, _("Stock items..."), group.get("search_stock_items")), ("SearchClosedStockItems", None, _("Closed stock items..."), group.get("search_closed_stock_items")), ("Suppliers", STOQ_SUPPLIERS, _("Suppliers..."), group.get("search_suppliers")), ("Transporter", None, _("Transporters..."), group.get("search_transporters")), ("SearchQuotes", None, _("Quotes..."), group.get("search_quotes")), ("SearchPurchasedItems", None, _("Purchased items..."), group.get("search_purchased_items")), ("ProductsSoldSearch", None, _("Sold products..."), group.get("search_products_sold")), ("ProductsPriceSearch", None, _("Prices..."), group.get("search_prices")), ("SearchInConsignmentItems", None, _("Search consigment items..."), group.get("search_consignment_items")), # Order ("Confirm", gtk.STOCK_APPLY, _("Confirm..."), group.get('order_confirm'), _("Confirm the selected order(s), marking it as sent to the " "supplier")), ("Cancel", gtk.STOCK_CANCEL, _("Cancel..."), group.get('order_cancel'), _("Cancel the selected order")), ("Edit", gtk.STOCK_EDIT, _("Edit..."), group.get('order_edit'), _("Edit the selected order, allowing you to change it's details")), ("Details", gtk.STOCK_INFO, _("Details..."), group.get('order_details'), _("Show details of the selected order")), ("Finish", gtk.STOCK_APPLY, _("Finish..."), group.get('order_finish'), _('Complete the selected partially received order')), ] self.purchase_ui = self.add_ui_actions("", actions, filename="purchase.xml") self.Confirm.props.is_important = True self.NewOrder.set_short_label(_("New order")) self.NewQuote.set_short_label(_("New quote")) self.Products.set_short_label(_("Products")) self.Suppliers.set_short_label(_("Suppliers")) self.Confirm.set_short_label(_("Confirm")) self.Cancel.set_short_label(_("Cancel")) self.Finish.set_short_label(_("Finish")) self.Edit.set_short_label(_("Edit")) self.Details.set_short_label(_("Details")) self.set_help_section(_("Purchase help"), 'app-purchase') self.popup = self.uimanager.get_widget('/PurchaseSelection') def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.window.add_new_items([ self.NewOrder, self.NewQuote, self.NewProduct, self.NewConsignment]) self.window.add_search_items([ self.Products, self.Suppliers, self.SearchQuotes, self.Services]) self.search.set_summary_label(column='total', label=('<b>%s</b>' % api.escape(_('Orders total:'))), format='<b>%s</b>', parent=self.get_statusbar_message_area()) self.results.set_selection_mode(gtk.SELECTION_MULTIPLE) self.Confirm.set_sensitive(False) self._inventory_widgets = [self.NewConsignment, self.CloseInConsignment] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def activate(self, refresh=True): self.window.NewToolItem.set_tooltip( _("Create a new purchase order")) self.window.SearchToolItem.set_tooltip( _("Search for purchase orders")) self.window.Print.set_tooltip( _("Print a report of these orders")) if refresh: self._update_view() self.results.set_selection_mode(gtk.SELECTION_MULTIPLE) self.check_open_inventory() self.search.focus_search_entry() def setup_focus(self): self.refresh() def deactivate(self): self.uimanager.remove_ui(self.purchase_ui) def new_activate(self): self._new_order() def search_activate(self): self.run_dialog(ProductSearch, self.store) def search_completed(self, results, states): if len(results): return supplier, status = states[:2] if len(states) > 2 or (supplier.text == '' and status.value is None): self.search.set_message("%s\n\n%s" % ( _("No orders could be found."), _("Would you like to %s ?") % ( '<a href="new_order">%s</a>' % ( api.escape(_("create a new order"), ))) )) # FIXME: Push number of results to Statusbar def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.set_text_field_columns(['supplier_name', 'identifier_str']) self.status_filter = ComboSearchFilter(_('Show orders'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) self.branch_filter = self.create_branch_filter(column=PurchaseOrderView.branch_id) def get_columns(self): return [IdentifierColumn('identifier', title=_('Purchase #')), SearchColumn('status_str', title=_('Status'), width=100, data_type=str, search_attribute='status', valid_values=self._get_status_values(), visible=False), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), width=90, data_type=datetime.date, sorted=True, order=gtk.SORT_DESCENDING), SearchColumn('supplier_name', title=_('Supplier'), data_type=str, searchable=True, expand=True, ellipsize=pango.ELLIPSIZE_END), SearchColumn('ordered_quantity', title=_('Ordered'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('received_quantity', title=_('Received'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('total', title=_('Total'), data_type=currency, width=120)] def print_report(self, *args, **kwargs): # PurchaseReport needs a status arg kwargs['status'] = self.status_filter.get_state().value super(PurchaseApp, self).print_report(*args, **kwargs) def search_for_date(self, date): dfilter = DateSearchFilter(_("Expected receival date")) dfilter.set_removable() dfilter.select(data=DateSearchFilter.Type.USER_DAY) self.add_filter(dfilter, columns=["expected_receival_date"]) dfilter.start_date.set_date(date) self.refresh() # # Private # def _update_totals(self): self._update_view() def _update_list_aware_widgets(self, has_items): for widget in (self.Edit, self.Details): widget.set_sensitive(has_items) def _update_view(self): self._update_list_aware_widgets(len(self.results)) selection = self.results.get_selected_rows() can_edit = one_selected = len(selection) == 1 can_finish = False if selection: can_send_supplier = all( order.status == PurchaseOrder.ORDER_PENDING for order in selection) can_cancel = all(order_view.purchase.can_cancel() for order_view in selection) else: can_send_supplier = False can_cancel = False if one_selected: can_edit = (selection[0].status == PurchaseOrder.ORDER_PENDING or selection[0].status == PurchaseOrder.ORDER_QUOTING or selection[0].status == PurchaseOrder.ORDER_CONFIRMED) can_finish = (selection[0].status == PurchaseOrder.ORDER_CONFIRMED and selection[0].received_quantity > 0) self.Cancel.set_sensitive(can_cancel) self.Edit.set_sensitive(can_edit) self.Confirm.set_sensitive(can_send_supplier) self.Details.set_sensitive(one_selected) self.Finish.set_sensitive(can_finish) def _new_order(self, order=None, edit_mode=False): with api.new_store() as store: order = store.fetch(order) self.run_dialog(PurchaseWizard, store, order, edit_mode) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) def _edit_order(self): selected = self.results.get_selected_rows() qty = len(selected) if qty != 1: raise ValueError('You should have only one order selected, ' 'got %d instead' % qty) purchase = selected[0].purchase if purchase.status == PurchaseOrder.ORDER_QUOTING: self._quote_order(purchase) else: self._new_order(purchase, edit_mode=False) def _run_details_dialog(self): order_views = self.results.get_selected_rows() qty = len(order_views) if qty != 1: raise ValueError('You should have only one order selected ' 'at this point, got %d' % qty) self.run_dialog(PurchaseDetailsDialog, self.store, model=order_views[0].purchase) def _send_selected_items_to_supplier(self): orders = self.results.get_selected_rows() valid_order_views = [ order for order in orders if order.status == PurchaseOrder.ORDER_PENDING] if not valid_order_views: warning(_("There are no pending orders selected.")) return msg = stoqlib_ngettext( _("The selected order will be marked as sent."), _("The %d selected orders will be marked as sent.") % len(valid_order_views), len(valid_order_views)) confirm_label = stoqlib_ngettext(_("Confirm order"), _("Confirm orders"), len(valid_order_views)) if not yesno(msg, gtk.RESPONSE_YES, confirm_label, _("Don't confirm")): return with api.new_store() as store: for order_view in valid_order_views: order = store.fetch(order_view.purchase) order.confirm() self.refresh() self.select_result(orders) def _finish_order(self): order_views = self.results.get_selected_rows() qty = len(order_views) if qty != 1: raise ValueError('You should have only one order selected ' 'at this point, got %d' % qty) with api.new_store() as store: order = store.fetch(order_views[0].purchase) self.run_dialog(PurchaseFinishWizard, store, order) self.refresh() self.select_result(order_views) def _cancel_order(self): order_views = self.results.get_selected_rows() assert all(ov.purchase.can_cancel() for ov in order_views) cancel_label = stoqlib_ngettext(_("Cancel order"), _("Cancel orders"), len(order_views)) select_label = stoqlib_ngettext(_('The selected order will be cancelled.'), _('The selected orders will be cancelled.'), len(order_views)) if not yesno(select_label, gtk.RESPONSE_YES, cancel_label, _("Don't cancel")): return with api.new_store() as store: for order_view in order_views: order = store.fetch(order_view.purchase) order.cancel() self._update_totals() self.refresh() self.select_result(order_views) def _get_status_values(self): items = [(text, value) for value, text in PurchaseOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items def _quote_order(self, quote=None): with api.new_store() as store: quote = store.fetch(quote) self.run_dialog(QuotePurchaseWizard, store, quote) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) def _new_product(self): ProductCreateWizard.run_wizard(self) def _new_consignment(self): with api.new_store() as store: self.run_dialog(ConsignmentWizard, store, model=None) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) # # Kiwi Callbacks # def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) def on_results__row_activated(self, klist, purchase_order_view): self._run_details_dialog() def on_results__selection_changed(self, results, selected): self._update_view() def on_results__activate_link(self, results, uri): if uri == 'new_order': self._new_order() def _on_results__double_click(self, results, order): self._run_details_dialog() def _on_results__has_rows(self, results, has_items): self._update_list_aware_widgets(has_items) def on_Details__activate(self, action): self._run_details_dialog() def on_Edit__activate(self, action): self._edit_order() def on_Cancel__activate(self, action): self._cancel_order() def on_Confirm__activate(self, button): self._send_selected_items_to_supplier() def on_Finish__activate(self, action): self._finish_order() # Order def on_StockCost__activate(self, action): self.run_dialog(StockCostDialog, self.store, None) # Consignment def on_CloseInConsignment__activate(self, action): with api.new_store() as store: self.run_dialog(CloseInConsignmentWizard, store) def on_SearchInConsignmentItems__activate(self, action): self.run_dialog(ConsignmentItemSearch, self.store) def on_Categories__activate(self, action): self.run_dialog(SellableCategorySearch, self.store) def on_SearchQuotes__activate(self, action): with api.new_store() as store: self.run_dialog(ReceiveQuoteWizard, store) self.refresh() def on_SearchPurchasedItems__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_Suppliers__activate(self, action): self.run_dialog(SupplierSearch, self.store, hide_footer=True) def on_Products__activate(self, action): self.run_dialog(ProductSearch, self.store) def on_ProductUnits__activate(self, action): self.run_dialog(SellableUnitSearch, self.store) def on_ProductManufacturers__activate(self, action): self.run_dialog(ProductManufacturerDialog, self.store) def on_Services__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_Transporter__activate(self, action): self.run_dialog(TransporterSearch, self.store, hide_footer=True) def on_ProductsSoldSearch__activate(self, action): self.run_dialog(ProductsSoldSearch, self.store) def on_ProductsPriceSearch__activate(self, action): from stoqlib.domain.person import ClientCategory if not self.store.find(ClientCategory).count(): warning(_("Can't use prices editor without client categories")) return with api.new_store() as store: self.run_dialog(SellablePriceDialog, store) # Toolitem def on_NewOrder__activate(self, action): self._new_order() def on_NewQuote__activate(self, action): self._quote_order() def on_NewProduct__activate(self, action): self._new_product() def on_NewConsignment__activate(self, action): self._new_consignment()
class ProductionApp(ShellApp): app_title = _(u'Production') gladefile = "production" search_spec = ProductionOrder search_label = _(u'matching:') report_table = ProductionReport # # Application # def create_actions(self): group = get_accels('app.production') actions = [ ('menubar', None, ''), # File ('NewProduction', gtk.STOCK_NEW, _('Production order...'), group.get('new_production_order'), _('Create a new production')), ('ProductionPurchaseQuote', STOQ_PRODUCTION_APP, _('Purchase quote...'), group.get('new_production_quote')), # Production ('ProductionMenu', None, _('Production')), ('StartProduction', gtk.STOCK_CONVERT, _('Start production...'), group.get('production_start'), _('Start the selected production')), ('EditProduction', gtk.STOCK_EDIT, _('Edit production...'), group.get('production_edit'), _('Edit the selected production')), ('ProductionDetails', gtk.STOCK_INFO, _('Production details...'), group.get('production_details'), _('Show production details and register produced items')), # Search ("SearchProduct", None, _("Production products..."), group.get('search_production_products'), _("Search for production products")), ("SearchService", None, _("Services..."), group.get('search_services'), _("Search for services")), ("SearchProductionItem", STOQ_PRODUCTION_APP, _("Production items..."), group.get('search_production_items'), _("Search for production items")), ("SearchProductionHistory", None, _("Production history..."), group.get('search_production_history'), _("Search for production history")), ] self.production_ui = self.add_ui_actions("", actions, filename="production.xml") self.set_help_section(_("Production help"), 'app-production') self.NewProduction.set_short_label(_("New Production")) self.ProductionPurchaseQuote.set_short_label(_("Purchase")) self.SearchProductionItem.set_short_label(_("Search items")) self.StartProduction.set_short_label(_('Start')) self.EditProduction.set_short_label(_('Edit')) self.ProductionDetails.set_short_label(_('Details')) self.StartProduction.props.is_important = True def create_ui(self): self.popup = self.uimanager.get_widget('/ProductionSelection') self.window.add_new_items([self.NewProduction, self.ProductionPurchaseQuote]) self.window.add_search_items([ self.SearchProduct, self.SearchService, self.SearchProductionItem, ]) self.window.Print.set_tooltip( _("Print a report of these productions")) self._inventory_widgets = [self.StartProduction] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def activate(self, refresh=True): if refresh: self.refresh() self._update_widgets() self.check_open_inventory() def deactivate(self): self.uimanager.remove_ui(self.production_ui) def new_activate(self): self._open_production_order() def search_activate(self): self.run_dialog(ProductionProductSearch, self.store) def create_filters(self): self.set_text_field_columns(['description']) self.status_filter = ComboSearchFilter( _(u'Show productions with status'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) def get_columns(self): return [IdentifierColumn('identifier', sorted=True, order=gtk.SORT_DESCENDING), Column('status_string', title=_(u'Status'), data_type=str, visible=False), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), Column('responsible.person.name', title=_(u'Responsible'), data_type=str, width=150), SearchColumn('open_date', title=_(u'Opened'), data_type=datetime.date, width=80), SearchColumn('close_date', title=_(u'Closed'), data_type=datetime.date, width=80)] def print_report(self, *args, **kwargs): # ProductionReport needs a status kwarg kwargs['status'] = self.status_filter.get_state().value super(ProductionApp, self).print_report(*args, **kwargs) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) # # Private # def _update_widgets(self): selected = self.results.get_selected() can_edit = False can_start = False if selected: can_edit = (selected.status == ProductionOrder.ORDER_OPENED or selected.status == ProductionOrder.ORDER_WAITING) can_start = can_edit self.set_sensitive([self.EditProduction], can_edit) self.set_sensitive([self.StartProduction], can_start) self.set_sensitive([self.ProductionDetails], bool(selected)) def _get_status_values(self): items = [(text, value) for value, text in ProductionOrder.statuses.items()] items.insert(0, (_(u'Any'), None)) return items def _open_production_order(self, order=None): store = api.new_store() order = store.fetch(order) retval = self.run_dialog(ProductionWizard, store, order) store.confirm(retval) store.close() self.refresh() def _start_production_order(self): store = api.new_store() order = store.fetch(self.results.get_selected()) assert order is not None retval = self.run_dialog(StartProductionDialog, store, order) store.confirm(retval) store.close() self.refresh() def _production_details(self): order = self.results.get_selected() assert order is not None store = api.new_store() model = store.fetch(order) self.run_dialog(ProductionDetailsDialog, store, model) store.confirm(True) store.close() # # Kiwi Callbacks # def on_EditProduction__activate(self, widget): order = self.results.get_selected() assert order is not None self._open_production_order(order) def on_ProductionDetails__activate(self, widget): self._production_details() def on_results__selection_changed(self, results, selected): self._update_widgets() def on_results__has_rows(self, widget, has_rows): self._update_widgets() def on_results__row_activated(self, widget, order): self._production_details() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) # Production def on_NewProduction__activate(self, action): self._open_production_order() def on_StartProduction__activate(self, action): self._start_production_order() def on_ProductionPurchaseQuote__activate(self, action): with api.trans() as store: self.run_dialog(ProductionQuoteDialog, store) # Search def on_SearchProduct__activate(self, action): self.run_dialog(ProductionProductSearch, self.store) def on_SearchService__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_SearchProductionItem__activate(self, action): self.run_dialog(ProductionItemsSearch, self.store) def on_SearchProductionHistory__activate(self, action): self.run_dialog(ProductionHistorySearch, self.store)
class BaseAccountWindow(ShellApp): # # Application # def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.results.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.search.set_summary_label(column='value', label='<b>%s</b>' % (_('Total'), ), format='<b>%s</b>', parent=self.get_statusbar_message_area()) self.results.set_cell_data_func(self._on_results__cell_data_func) def search_completed(self, results, states): if len(results): return state = states[1] if state and state.value is None: not_found = _("No payments found.") payment_url = '<a href="new_payment">%s</a>?' % ( api.escape(_("create a new payment"))) new_payment = _("Would you like to %s") % (payment_url, ) msg = "%s\n\n%s" % (not_found, new_payment) else: v = state.value.value if v == 'status:late': msg = _("No late payments found.") elif v == 'status:paid': msg = _("No paid payments found.") elif v == 'status:not-paid': if self.search_spec == InPaymentView: msg = _("No payments to receive found.") else: msg = _("No payments to pay found.") elif v.startswith('category:'): category = v.split(':')[1] not_found = _("No payments in the <b>%s</b> category were found.") % ( api.escape(category), ) payment_url = '<a href="new_payment?%s">%s</a>?' % ( urllib.parse.quote(category), _("create a new payment")) msg = "%s\n\n%s" % ( not_found, _("Would you like to %s") % (payment_url, )) else: return self.search.set_message(msg) # # Public API # def add_payment(self, category=None): with api.new_store() as store: self.run_dialog(self.editor_class, store, category=category) if store.committed: self._update_filter_items() self.refresh() self.select_result(self.store.find(self.search_spec, id=store.retval.id).one()) def show_details(self, payment_view): """Shows some details about the payment, allowing to edit a few properties """ with api.new_store() as store: payment = store.fetch(payment_view.payment) run_dialog(self.editor_class, self, store, payment) if store.committed: payment_view.sync() self.results.update(payment_view) return payment def show_comments(self, payment_view): """Shows a dialog with comments saved on the payment @param payment_view: an OutPaymentView or InPaymentView instance """ with api.new_store() as store: run_dialog(PaymentCommentsDialog, self, store, payment_view.payment) if store.committed: payment_view.sync() self.results.update(payment_view) def change_due_date(self, payment_view, order): """ Receives a payment_view and change the payment due date related to the view. :param payment_view: an OutPaymentView or InPaymentView instance :param order: a Sale or Purchase instance related to this payment. This will be used to show the identifier of the order """ assert payment_view.can_change_due_date() with api.new_store() as store: payment = store.fetch(payment_view.payment) order = store.fetch(order) run_dialog(PaymentDueDateChangeDialog, self, store, payment, order) if store.committed: # We need to refresh the whole list as the payment(s) can possibly # disappear for the selected view self.refresh() def change_status(self, payment_view, order, status): """Show a dialog do enter a reason for status change :param payment_view: an OutPaymentView or InPaymentView instance :param order: a Sale or Purchase instance related to this payment. This will be used to show the identifier of the order :param status: The new status to set the payment to """ with api.new_store() as store: payment = store.fetch(payment_view.payment) order = store.fetch(payment_view.sale) if order is None: order = store.fetch(payment_view.purchase) run_dialog(PaymentStatusChangeDialog, self, store, payment, status, order) if store.committed: # We need to refresh the whole list as the payment(s) can possibly # disappear for the selected view self.refresh() def create_main_filter(self): self.main_filter = ComboSearchFilter(_('Show'), []) combo = self.main_filter.combo combo.color_attribute = 'color' combo.set_row_separator_func(self._on_main_filter__row_separator_func) self._update_filter_items() executer = self.search.get_query_executer() executer.add_filter_query_callback( self.main_filter, self._create_main_query) self.add_filter(self.main_filter, SearchFilterPosition.TOP) self.create_branch_filter(column=self.search_spec.branch_id) def add_filter_items(self, category_type, options): categories = PaymentCategory.get_by_type(self.store, category_type) items = [(_('All payments'), None)] if categories.count() > 0: options.append(FilterItem('sep', 'sep')) items.extend([(item.name, item) for item in options]) for c in categories: item = FilterItem(c.name, 'category:%s' % (c.name, ), color=c.color, item_id=c.id) items.append((item.name, item)) self.main_filter.update_values(items) # # Private # def _create_main_query(self, state): item = state.value if item is None: return None kind, value = item.value.split(':') payment_view = self.search_spec if kind == 'status': if value == 'paid': return payment_view.status == Payment.STATUS_PAID elif value == 'not-paid': return payment_view.status == Payment.STATUS_PENDING elif value == 'late': tolerance = api.sysparam.get_int('TOLERANCE_FOR_LATE_PAYMENTS') return And( payment_view.status == Payment.STATUS_PENDING, payment_view.due_date < localtoday() - relativedelta(days=tolerance)) elif kind == 'category': return payment_view.category == value raise AssertionError(kind, value) def _show_payment_categories(self): store = api.new_store() self.run_dialog(PaymentCategoryDialog, store, self.payment_category_type) self._update_filter_items() store.close() # # Callbacks # def _on_main_filter__row_separator_func(self, model, titer): if model[titer][0] == 'sep': return True return False def _on_results__cell_data_func(self, column, renderer, pv, text): if not isinstance(renderer, Gtk.CellRendererText): return text state = self.main_filter.get_state() def show_strikethrough(): if state.value is None: return True if state.value.value.startswith('category:'): return True return False is_pending = (pv.status == Payment.STATUS_PENDING) show_strikethrough = not is_pending and show_strikethrough() is_late = pv.is_late() renderer.set_property('strikethrough-set', show_strikethrough) renderer.set_property('weight-set', is_late) if show_strikethrough: renderer.set_property('strikethrough', True) if is_late: renderer.set_property('weight', Pango.Weight.BOLD) return text def on_results__activate_link(self, results, uri): if uri.startswith('new_payment'): if '?' in uri: category = urllib.parse.unquote(uri.split('?', 1)[1]) else: category = None self.add_payment(category=category) def on_PaymentFlowHistory__activate(self, action): self.run_dialog(PaymentFlowHistoryDialog, self.store) def on_PaymentCategories__activate(self, action): self._show_payment_categories()
class ProductionApp(ShellApp): app_title = _(u'Production') gladefile = "production" search_spec = ProductionOrder search_label = _(u'matching:') report_table = ProductionReport # # Application # def create_actions(self): group = get_accels('app.production') actions = [ ('menubar', None, ''), # File ('NewProduction', Gtk.STOCK_NEW, _('Production order...'), group.get('new_production_order'), _('Create a new production')), ('ProductionPurchaseQuote', None, _('Purchase quote...'), group.get('new_production_quote')), # Production ('ProductionMenu', None, _('Production')), ('StartProduction', Gtk.STOCK_CONVERT, _('Start production...'), group.get('production_start'), _('Start the selected production')), ('EditProduction', Gtk.STOCK_EDIT, _('Edit production...'), group.get('production_edit'), _('Edit the selected production')), ('FinalizeProduction', Gtk.STOCK_APPLY, _('Finalize production...'), None, _('Finalize the selected production')), ('CancelProduction', Gtk.STOCK_CANCEL, _('Cancel production...'), None, _('Cancel the selected production')), ('ProductionDetails', Gtk.STOCK_INFO, _('Production details...'), group.get('production_details'), _('Show production details and register produced items')), # Search ("SearchProduct", None, _("Production products..."), group.get('search_production_products'), _("Search for production products")), ("SearchService", None, _("Services..."), group.get('search_services'), _("Search for services")), ("SearchProductionItem", None, _("Production items..."), group.get('search_production_items'), _("Search for production items")), ("SearchProductionHistory", None, _("Production history..."), group.get('search_production_history'), _("Search for production history")), ] self.production_ui = self.add_ui_actions(actions) self.set_help_section(_("Production help"), 'app-production') def create_ui(self): self.window.add_new_items([self.NewProduction, self.ProductionPurchaseQuote]) self.window.add_search_items([ self.SearchProduct, self.SearchService, self.SearchProductionItem, self.SearchProductionHistory, ]) self._inventory_widgets = [self.StartProduction] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'production.ProductionDetails', True), ('fa-edit-symbolic', _('Edit production'), 'production.EditProduction', True), ('fa-play-symbolic', _('Start production'), 'production.StartProduction', True), ('fa-stop-symbolic', _('Finalize production'), 'production.FinalizeProduction', True), ('fa-ban-symbolic', _('Cancel production'), 'production.CancelProduction', True), ] return options def activate(self, refresh=True): if refresh: self.refresh() self._update_widgets() self.check_open_inventory() self.search.focus_search_entry() def create_filters(self): self.set_text_field_columns(['description']) self.status_filter = ComboSearchFilter( _(u'Show productions with status'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) def get_columns(self): return [IdentifierColumn('identifier', title=_('Production #'), sorted=True, order=Gtk.SortType.DESCENDING), Column('status_string', title=_(u'Status'), data_type=str, visible=False), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), Column('responsible.person.name', title=_(u'Responsible'), data_type=str, width=150), SearchColumn('open_date', title=_(u'Opened'), data_type=datetime.date, width=80), SearchColumn('close_date', title=_(u'Closed'), data_type=datetime.date, width=80), SearchColumn('cancel_date', title=_(u'Cancelled'), data_type=datetime.date, width=80)] def print_report(self, *args, **kwargs): # ProductionReport needs a status kwarg kwargs['status'] = self.status_filter.get_state().value super(ProductionApp, self).print_report(*args, **kwargs) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) # # Private # def _update_widgets(self): selected = self.results.get_selected() can_edit = False can_start = False can_finalize = False can_cancel = False if selected: can_edit = (selected.status == ProductionOrder.ORDER_OPENED or selected.status == ProductionOrder.ORDER_WAITING) can_start = can_edit can_finalize = (selected.status == ProductionOrder.ORDER_PRODUCING) can_cancel = can_edit self.set_sensitive([self.EditProduction], can_edit) self.set_sensitive([self.StartProduction], can_start) self.set_sensitive([self.FinalizeProduction], can_finalize) self.set_sensitive([self.CancelProduction], can_cancel) self.set_sensitive([self.ProductionDetails], bool(selected)) def _get_status_values(self): items = [(text, value) for value, text in ProductionOrder.statuses.items()] items.insert(0, (_(u'Any'), None)) return items def _open_production_order(self, order=None): store = api.new_store() order = store.fetch(order) retval = self.run_dialog(ProductionWizard, store, order) store.confirm(retval) store.close() self.refresh() def _start_production_order(self): store = api.new_store() order = store.fetch(self.results.get_selected()) assert order is not None retval = self.run_dialog(StartProductionDialog, store, order) store.confirm(retval) store.close() self.refresh() def _production_details(self): order = self.results.get_selected() assert order is not None store = api.new_store() model = store.fetch(order) initial_status = model.status self.run_dialog(ProductionDetailsDialog, store, model) store.confirm(True) if initial_status != model.status: self.refresh() store.close() def _finalize_production(self): if not yesno(_("The selected order will be finalized."), Gtk.ResponseType.YES, _("Finalize order"), _("Don't finalize")): return with api.new_store() as store: model = store.fetch(self.results.get_selected()) model.try_finalize_production(ignore_completion=True) self.refresh() def _cancel_production(self): if not yesno(_("The selected order will be cancelled."), Gtk.ResponseType.YES, _("Cancel order"), _("Don't cancel")): return with api.new_store() as store: order = self.results.get_selected() model = store.fetch(order) model.cancel() self.refresh() # # Kiwi Callbacks # def on_EditProduction__activate(self, widget): order = self.results.get_selected() assert order is not None self._open_production_order(order) def on_ProductionDetails__activate(self, widget): self._production_details() def on_FinalizeProduction__activate(self, widget): self._finalize_production() def on_CancelProduction__activate(self, widget): self._cancel_production() def on_results__selection_changed(self, results, selected): self._update_widgets() def on_results__has_rows(self, widget, has_rows): self._update_widgets() def on_results__row_activated(self, widget, order): self._production_details() # Production def on_NewProduction__activate(self, action): self._open_production_order() def on_StartProduction__activate(self, action): self._start_production_order() def on_ProductionPurchaseQuote__activate(self, action): with api.new_store() as store: self.run_dialog(ProductionQuoteDialog, store) # Search def on_SearchProduct__activate(self, action): self.run_dialog(ProductionProductSearch, self.store) def on_SearchService__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_SearchProductionItem__activate(self, action): self.run_dialog(ProductionItemsSearch, self.store) def on_SearchProductionHistory__activate(self, action): self.run_dialog(ProductionHistorySearch, 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 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 BaseAccountWindow(ShellApp): # # Application # def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.results.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.search.set_summary_label(column='value', label='<b>%s</b>' % (_('Total'), ), format='<b>%s</b>', parent=self.get_statusbar_message_area()) self.results.set_cell_data_func(self._on_results__cell_data_func) def search_completed(self, results, states): if len(results): return state = states[1] if state and state.value is None: not_found = _("No payments found.") payment_url = '<a href="new_payment">%s</a>?' % (api.escape( _("create a new payment"))) new_payment = _("Would you like to %s") % (payment_url, ) msg = "%s\n\n%s" % (not_found, new_payment) else: v = state.value.value if v == 'status:late': msg = _("No late payments found.") elif v == 'status:paid': msg = _("No paid payments found.") elif v == 'status:not-paid': if self.search_spec == InPaymentView: msg = _("No payments to receive found.") else: msg = _("No payments to pay found.") elif v.startswith('category:'): category = v.split(':')[1] not_found = _( "No payments in the <b>%s</b> category were found.") % ( api.escape(category), ) payment_url = '<a href="new_payment?%s">%s</a>?' % ( urllib.parse.quote(category), _("create a new payment")) msg = "%s\n\n%s" % (not_found, _("Would you like to %s") % (payment_url, )) else: return self.search.set_message(msg) # # Public API # def add_payment(self, category=None): with api.new_store() as store: self.run_dialog(self.editor_class, store, category=category) if store.committed: self._update_filter_items() self.refresh() self.select_result( self.store.find(self.search_spec, id=store.retval.id).one()) def show_details(self, payment_view): """Shows some details about the payment, allowing to edit a few properties """ with api.new_store() as store: payment = store.fetch(payment_view.payment) run_dialog(self.editor_class, self, store, payment) if store.committed: payment_view.sync() self.results.update(payment_view) return payment def show_comments(self, payment_view): """Shows a dialog with comments saved on the payment @param payment_view: an OutPaymentView or InPaymentView instance """ with api.new_store() as store: run_dialog(PaymentCommentsDialog, self, store, payment_view.payment) if store.committed: payment_view.sync() self.results.update(payment_view) def change_due_date(self, payment_view, order): """ Receives a payment_view and change the payment due date related to the view. :param payment_view: an OutPaymentView or InPaymentView instance :param order: a Sale or Purchase instance related to this payment. This will be used to show the identifier of the order """ assert payment_view.can_change_due_date() with api.new_store() as store: payment = store.fetch(payment_view.payment) order = store.fetch(order) run_dialog(PaymentDueDateChangeDialog, self, store, payment, order) if store.committed: # We need to refresh the whole list as the payment(s) can possibly # disappear for the selected view self.refresh() def change_status(self, payment_view, order, status): """Show a dialog do enter a reason for status change :param payment_view: an OutPaymentView or InPaymentView instance :param order: a Sale or Purchase instance related to this payment. This will be used to show the identifier of the order :param status: The new status to set the payment to """ with api.new_store() as store: payment = store.fetch(payment_view.payment) order = store.fetch(payment_view.sale) if order is None: order = store.fetch(payment_view.purchase) run_dialog(PaymentStatusChangeDialog, self, store, payment, status, order) if store.committed: # We need to refresh the whole list as the payment(s) can possibly # disappear for the selected view self.refresh() def create_main_filter(self): self.main_filter = ComboSearchFilter(_('Show'), []) combo = self.main_filter.combo combo.color_attribute = 'color' combo.set_row_separator_func(self._on_main_filter__row_separator_func) self._update_filter_items() executer = self.search.get_query_executer() executer.add_filter_query_callback(self.main_filter, self._create_main_query) self.add_filter(self.main_filter, SearchFilterPosition.TOP) self.create_branch_filter(column=self.search_spec.branch_id) def add_filter_items(self, category_type, options): categories = PaymentCategory.get_by_type(self.store, category_type) items = [(_('All payments'), None)] if categories.count() > 0: options.append(FilterItem('sep', 'sep')) items.extend([(item.name, item) for item in options]) for c in categories: item = FilterItem(c.name, 'category:%s' % (c.name, ), color=c.color, item_id=c.id) items.append((item.name, item)) self.main_filter.update_values(items) # # Private # def _create_main_query(self, state): item = state.value if item is None: return None kind, value = item.value.split(':') payment_view = self.search_spec if kind == 'status': if value == 'paid': return payment_view.status == Payment.STATUS_PAID elif value == 'not-paid': return payment_view.status == Payment.STATUS_PENDING elif value == 'late': tolerance = api.sysparam.get_int('TOLERANCE_FOR_LATE_PAYMENTS') return And( payment_view.status == Payment.STATUS_PENDING, payment_view.due_date < localtoday() - relativedelta(days=tolerance)) elif kind == 'category': return payment_view.category == value raise AssertionError(kind, value) def _show_payment_categories(self): store = api.new_store() self.run_dialog(PaymentCategoryDialog, store, self.payment_category_type) self._update_filter_items() store.close() # # Callbacks # def _on_main_filter__row_separator_func(self, model, titer): if model[titer][0] == 'sep': return True return False def _on_results__cell_data_func(self, column, renderer, pv, text): if not isinstance(renderer, Gtk.CellRendererText): return text state = self.main_filter.get_state() def show_strikethrough(): if state.value is None: return True if state.value.value.startswith('category:'): return True return False is_pending = (pv.status == Payment.STATUS_PENDING) show_strikethrough = not is_pending and show_strikethrough() is_late = pv.is_late() renderer.set_property('strikethrough-set', show_strikethrough) renderer.set_property('weight-set', is_late) if show_strikethrough: renderer.set_property('strikethrough', True) if is_late: renderer.set_property('weight', Pango.Weight.BOLD) return text def on_results__activate_link(self, results, uri): if uri.startswith('new_payment'): if '?' in uri: category = urllib.parse.unquote(uri.split('?', 1)[1]) else: category = None self.add_payment(category=category) def on_PaymentFlowHistory__activate(self, action): self.run_dialog(PaymentFlowHistoryDialog, self.store) def on_PaymentCategories__activate(self, action): self._show_payment_categories()
class ProductionApp(ShellApp): app_title = _(u"Production") gladefile = "production" search_spec = ProductionOrder search_label = _(u"matching:") report_table = ProductionReport # # Application # def create_actions(self): group = get_accels("app.production") actions = [ ("menubar", None, ""), # File ( "NewProduction", gtk.STOCK_NEW, _("Production order..."), group.get("new_production_order"), _("Create a new production"), ), ("ProductionPurchaseQuote", STOQ_PRODUCTION_APP, _("Purchase quote..."), group.get("new_production_quote")), # Production ("ProductionMenu", None, _("Production")), ( "StartProduction", gtk.STOCK_CONVERT, _("Start production..."), group.get("production_start"), _("Start the selected production"), ), ( "EditProduction", gtk.STOCK_EDIT, _("Edit production..."), group.get("production_edit"), _("Edit the selected production"), ), ( "FinalizeProduction", gtk.STOCK_APPLY, _("Finalize production..."), None, _("Finalize the selected production"), ), ( "CancelProduction", gtk.STOCK_CANCEL, _("Cancel production..."), None, _("Cancel the selected production"), ), ( "ProductionDetails", gtk.STOCK_INFO, _("Production details..."), group.get("production_details"), _("Show production details and register produced items"), ), # Search ( "SearchProduct", None, _("Production products..."), group.get("search_production_products"), _("Search for production products"), ), ("SearchService", None, _("Services..."), group.get("search_services"), _("Search for services")), ( "SearchProductionItem", STOQ_PRODUCTION_APP, _("Production items..."), group.get("search_production_items"), _("Search for production items"), ), ( "SearchProductionHistory", None, _("Production history..."), group.get("search_production_history"), _("Search for production history"), ), ] self.production_ui = self.add_ui_actions("", actions, filename="production.xml") self.set_help_section(_("Production help"), "app-production") self.NewProduction.set_short_label(_("New Production")) self.ProductionPurchaseQuote.set_short_label(_("Purchase")) self.SearchProductionItem.set_short_label(_("Search items")) self.StartProduction.set_short_label(_("Start")) self.EditProduction.set_short_label(_("Edit")) self.FinalizeProduction.set_short_label(_("Finalize")) self.CancelProduction.set_short_label(_("Cancel")) self.ProductionDetails.set_short_label(_("Details")) self.StartProduction.props.is_important = True self.FinalizeProduction.props.is_important = True def create_ui(self): self.popup = self.uimanager.get_widget("/ProductionSelection") self.window.add_new_items([self.NewProduction, self.ProductionPurchaseQuote]) self.window.add_search_items([self.SearchProduct, self.SearchService, self.SearchProductionItem]) self.window.Print.set_tooltip(_("Print a report of these productions")) self._inventory_widgets = [self.StartProduction] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def activate(self, refresh=True): if refresh: self.refresh() self._update_widgets() self.check_open_inventory() self.search.focus_search_entry() def deactivate(self): self.uimanager.remove_ui(self.production_ui) def new_activate(self): self._open_production_order() def search_activate(self): self.run_dialog(ProductionProductSearch, self.store) def create_filters(self): self.set_text_field_columns(["description"]) self.status_filter = ComboSearchFilter(_(u"Show productions with status"), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ["status"]) def get_columns(self): return [ IdentifierColumn("identifier", sorted=True, order=gtk.SORT_DESCENDING), Column("status_string", title=_(u"Status"), data_type=str, visible=False), SearchColumn("description", title=_(u"Description"), data_type=str, expand=True), Column("responsible.person.name", title=_(u"Responsible"), data_type=str, width=150), SearchColumn("open_date", title=_(u"Opened"), data_type=datetime.date, width=80), SearchColumn("close_date", title=_(u"Closed"), data_type=datetime.date, width=80), SearchColumn("cancel_date", title=_(u"Cancelled"), data_type=datetime.date, width=80), ] def print_report(self, *args, **kwargs): # ProductionReport needs a status kwarg kwargs["status"] = self.status_filter.get_state().value super(ProductionApp, self).print_report(*args, **kwargs) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) # # Private # def _update_widgets(self): selected = self.results.get_selected() can_edit = False can_start = False can_finalize = False can_cancel = False if selected: can_edit = ( selected.status == ProductionOrder.ORDER_OPENED or selected.status == ProductionOrder.ORDER_WAITING ) can_start = can_edit can_finalize = selected.status == ProductionOrder.ORDER_PRODUCING can_cancel = can_edit self.set_sensitive([self.EditProduction], can_edit) self.set_sensitive([self.StartProduction], can_start) self.set_sensitive([self.FinalizeProduction], can_finalize) self.set_sensitive([self.CancelProduction], can_cancel) self.set_sensitive([self.ProductionDetails], bool(selected)) def _get_status_values(self): items = [(text, value) for value, text in ProductionOrder.statuses.items()] items.insert(0, (_(u"Any"), None)) return items def _open_production_order(self, order=None): store = api.new_store() order = store.fetch(order) retval = self.run_dialog(ProductionWizard, store, order) store.confirm(retval) store.close() self.refresh() def _start_production_order(self): store = api.new_store() order = store.fetch(self.results.get_selected()) assert order is not None retval = self.run_dialog(StartProductionDialog, store, order) store.confirm(retval) store.close() self.refresh() def _production_details(self): order = self.results.get_selected() assert order is not None store = api.new_store() model = store.fetch(order) initial_status = model.status self.run_dialog(ProductionDetailsDialog, store, model) store.confirm(True) if initial_status != model.status: self.refresh() store.close() def _finalize_production(self): if not yesno( _("The selected order will be finalized."), gtk.RESPONSE_YES, _("Finalize order"), _("Don't finalize") ): return with api.new_store() as store: model = store.fetch(self.results.get_selected()) model.try_finalize_production(ignore_completion=True) self.refresh() def _cancel_production(self): if not yesno( _("The selected order will be cancelled."), gtk.RESPONSE_YES, _("Cancel order"), _("Don't cancel") ): return with api.new_store() as store: order = self.results.get_selected() model = store.fetch(order) model.cancel() self.refresh() # # Kiwi Callbacks # def on_EditProduction__activate(self, widget): order = self.results.get_selected() assert order is not None self._open_production_order(order) def on_ProductionDetails__activate(self, widget): self._production_details() def on_FinalizeProduction__activate(self, widget): self._finalize_production() def on_CancelProduction__activate(self, widget): self._cancel_production() def on_results__selection_changed(self, results, selected): self._update_widgets() def on_results__has_rows(self, widget, has_rows): self._update_widgets() def on_results__row_activated(self, widget, order): self._production_details() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) # Production def on_NewProduction__activate(self, action): self._open_production_order() def on_StartProduction__activate(self, action): self._start_production_order() def on_ProductionPurchaseQuote__activate(self, action): with api.new_store() as store: self.run_dialog(ProductionQuoteDialog, store) # Search def on_SearchProduct__activate(self, action): self.run_dialog(ProductionProductSearch, self.store) def on_SearchService__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_SearchProductionItem__activate(self, action): self.run_dialog(ProductionItemsSearch, self.store) def on_SearchProductionHistory__activate(self, action): self.run_dialog(ProductionHistorySearch, self.store)
class ProductionApp(ShellApp): app_title = _(u'Production') gladefile = "production" search_spec = ProductionOrder search_label = _(u'matching:') report_table = ProductionReport # # Application # def create_actions(self): group = get_accels('app.production') actions = [ ('menubar', None, ''), # File ('NewProduction', gtk.STOCK_NEW, _('Production order...'), group.get('new_production_order'), _('Create a new production')), ('ProductionPurchaseQuote', STOQ_PRODUCTION_APP, _('Purchase quote...'), group.get('new_production_quote')), # Production ('ProductionMenu', None, _('Production')), ('StartProduction', gtk.STOCK_CONVERT, _('Start production...'), group.get('production_start'), _('Start the selected production')), ('EditProduction', gtk.STOCK_EDIT, _('Edit production...'), group.get('production_edit'), _('Edit the selected production')), ('ProductionDetails', gtk.STOCK_INFO, _('Production details...'), group.get('production_details'), _('Show production details and register produced items')), # Search ("SearchProduct", None, _("Production products..."), group.get('search_production_products'), _("Search for production products")), ("SearchService", None, _("Services..."), group.get('search_services'), _("Search for services")), ("SearchProductionItem", STOQ_PRODUCTION_APP, _("Production items..."), group.get('search_production_items'), _("Search for production items")), ("SearchProductionHistory", None, _("Production history..."), group.get('search_production_history'), _("Search for production history")), ] self.production_ui = self.add_ui_actions("", actions, filename="production.xml") self.set_help_section(_("Production help"), 'app-production') self.NewProduction.set_short_label(_("New Production")) self.ProductionPurchaseQuote.set_short_label(_("Purchase")) self.SearchProductionItem.set_short_label(_("Search items")) self.StartProduction.set_short_label(_('Start')) self.EditProduction.set_short_label(_('Edit')) self.ProductionDetails.set_short_label(_('Details')) self.StartProduction.props.is_important = True def create_ui(self): self.popup = self.uimanager.get_widget('/ProductionSelection') self.window.add_new_items( [self.NewProduction, self.ProductionPurchaseQuote]) self.window.add_search_items([ self.SearchProduct, self.SearchService, self.SearchProductionItem, ]) self.window.Print.set_tooltip(_("Print a report of these productions")) self._inventory_widgets = [self.StartProduction] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def activate(self, params): self.search.refresh() self._update_widgets() self.check_open_inventory() def deactivate(self): self.uimanager.remove_ui(self.production_ui) def new_activate(self): self._open_production_order() def search_activate(self): self.run_dialog(ProductionProductSearch, self.store) def create_filters(self): self.set_text_field_columns(['description']) self.status_filter = ComboSearchFilter( _(u'Show productions with status'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) def get_columns(self): return [ IdentifierColumn('identifier', sorted=True, order=gtk.SORT_DESCENDING), Column('status_string', title=_(u'Status'), data_type=str, visible=False), SearchColumn('description', title=_(u'Description'), data_type=str, expand=True), Column('responsible.person.name', title=_(u'Responsible'), data_type=str, width=150), SearchColumn('open_date', title=_(u'Opened'), data_type=datetime.date, width=80), SearchColumn('close_date', title=_(u'Closed'), data_type=datetime.date, width=80) ] def print_report(self, *args, **kwargs): # ProductionReport needs a status kwarg kwargs['status'] = self.status_filter.get_state().value super(ProductionApp, self).print_report(*args, **kwargs) def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) # # Private # def _update_widgets(self): selected = self.results.get_selected() can_edit = False can_start = False if selected: can_edit = (selected.status == ProductionOrder.ORDER_OPENED or selected.status == ProductionOrder.ORDER_WAITING) can_start = can_edit self.set_sensitive([self.EditProduction], can_edit) self.set_sensitive([self.StartProduction], can_start) self.set_sensitive([self.ProductionDetails], bool(selected)) def _get_status_values(self): items = [(text, value) for value, text in ProductionOrder.statuses.items()] items.insert(0, (_(u'Any'), None)) return items def _open_production_order(self, order=None): store = api.new_store() order = store.fetch(order) retval = self.run_dialog(ProductionWizard, store, order) store.confirm(retval) store.close() self.refresh() def _start_production_order(self): store = api.new_store() order = store.fetch(self.results.get_selected()) assert order is not None retval = self.run_dialog(StartProductionDialog, store, order) store.confirm(retval) store.close() self.refresh() def _production_details(self): order = self.results.get_selected() assert order is not None store = api.new_store() model = store.fetch(order) self.run_dialog(ProductionDetailsDialog, store, model) store.confirm(True) store.close() # # Kiwi Callbacks # def on_EditProduction__activate(self, widget): order = self.results.get_selected() assert order is not None self._open_production_order(order) def on_ProductionDetails__activate(self, widget): self._production_details() def on_results__selection_changed(self, results, selected): self._update_widgets() def on_results__has_rows(self, widget, has_rows): self._update_widgets() def on_results__row_activated(self, widget, order): self._production_details() def on_results__right_click(self, results, result, event): self.popup.popup(None, None, None, event.button, event.time) # Production def on_NewProduction__activate(self, action): self._open_production_order() def on_StartProduction__activate(self, action): self._start_production_order() def on_ProductionPurchaseQuote__activate(self, action): with api.trans() as store: self.run_dialog(ProductionQuoteDialog, store) # Search def on_SearchProduct__activate(self, action): self.run_dialog(ProductionProductSearch, self.store) def on_SearchService__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_SearchProductionItem__activate(self, action): self.run_dialog(ProductionItemsSearch, self.store) def on_SearchProductionHistory__activate(self, action): self.run_dialog(ProductionHistorySearch, self.store)
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)