コード例 #1
0
ファイル: production.py プロジェクト: 5l1v3r1/stoq-1
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)
コード例 #2
0
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()
コード例 #3
0
ファイル: stock.py プロジェクト: rosalin/stoq
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)
コード例 #4
0
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)
コード例 #5
0
ファイル: purchase.py プロジェクト: amaurihamasu/stoq
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()
コード例 #6
0
ファイル: production.py プロジェクト: LeonamSilva/stoq
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)
コード例 #7
0
ファイル: accounts.py プロジェクト: hackedbellini/stoq
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()
コード例 #8
0
ファイル: production.py プロジェクト: hackedbellini/stoq
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)
コード例 #9
0
ファイル: stock.py プロジェクト: tmaxter/stoq
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)
コード例 #10
0
ファイル: stock.py プロジェクト: adrianoaguiar/stoq
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)
コード例 #11
0
ファイル: accounts.py プロジェクト: 5l1v3r1/stoq-1
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()
コード例 #12
0
ファイル: production.py プロジェクト: coletivoEITA/stoq
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)
コード例 #13
0
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)
コード例 #14
0
ファイル: stock.py プロジェクト: hackedbellini/stoq
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)