Example #1
0
    def __init__(self, store, model, visual_mode, ui_form_name, parent):
        self._parent = parent
        if ui_form_name:
            self.db_form = DatabaseForm(ui_form_name)
        else:
            self.db_form = None

        super(_PersonEditorTemplate, self).__init__(store,
                                                    model,
                                                    visual_mode=visual_mode)
Example #2
0
class _IndividualDocuments(BaseEditorSlave):
    model_type = Individual
    gladefile = 'IndividualDocuments'
    proxy_widgets = ('cpf',
                     'rg_expedition_date',
                     'rg_expedition_local',
                     'rg_number',
                     'state_registry',
                     'city_registry')

    def __init__(self, store, model, visual_mode=None, ui_form_name=None):
        self.db_form = DatabaseForm(ui_form_name) if ui_form_name else None
        BaseEditorSlave.__init__(self, store, model, visual_mode=visual_mode)

    def _setup_widgets(self):
        self.document_l10n = api.get_l10n_field('person_document')
        self.cpf_lbl.set_label(self.document_l10n.label + ':')
        self.cpf.set_mask(self.document_l10n.entry_mask)

    def _setup_form_fields(self):
        if not self.db_form:
            return

        # Do not update the widget if the model already has a responsible
        if self.model.responsible:
            return

        self.db_form.update_widget(self.cpf, other=self.cpf_lbl)

    def setup_proxies(self):
        self._setup_widgets()
        self._setup_form_fields()
        self.proxy = self.add_proxy(self.model,
                                    _IndividualDocuments.proxy_widgets)

    def on_cpf__validate(self, widget, value):
        # This will allow the user to use an empty value to this field
        if self.cpf.is_empty():
            return

        if not self.document_l10n.validate(value):
            return ValidationError(_('%s is not valid.') % (
                self.document_l10n.label,))

        if self.model.check_cpf_exists(value):
            return ValidationError(_('A person with this %s already exists') % (
                self.document_l10n.label,))
Example #3
0
    def __init__(self, store, model, visual_mode, ui_form_name, parent):
        self._parent = parent
        if ui_form_name:
            self.db_form = DatabaseForm(ui_form_name)
        else:
            self.db_form = None

        super(_PersonEditorTemplate, self).__init__(store, model,
                                                    visual_mode=visual_mode)
Example #4
0
class _IndividualDocuments(BaseEditorSlave):
    model_type = Individual
    gladefile = 'IndividualDocuments'
    proxy_widgets = ('cpf', 'rg_expedition_date', 'rg_expedition_local',
                     'rg_number', 'state_registry', 'city_registry')

    def __init__(self, store, model, visual_mode=None, ui_form_name=None):
        self.db_form = DatabaseForm(ui_form_name) if ui_form_name else None
        BaseEditorSlave.__init__(self, store, model, visual_mode=visual_mode)

    def _setup_widgets(self):
        self.document_l10n = api.get_l10n_field('person_document')
        self.cpf_lbl.set_label(self.document_l10n.label + ':')
        self.cpf.set_mask(self.document_l10n.entry_mask)

    def _setup_form_fields(self):
        if not self.db_form:
            return

        # Do not update the widget if the model already has a responsible
        if self.model.responsible:
            return

        self.db_form.update_widget(self.cpf, other=self.cpf_lbl)

    def setup_proxies(self):
        self._setup_widgets()
        self._setup_form_fields()
        self.proxy = self.add_proxy(self.model,
                                    _IndividualDocuments.proxy_widgets)

    def on_cpf__validate(self, widget, value):
        # This will allow the user to use an empty value to this field
        if self.cpf.is_empty():
            return

        if not self.document_l10n.validate(value):
            return ValidationError(
                _('%s is not valid.') % (self.document_l10n.label, ))

        if self.model.check_cpf_exists(value):
            return ValidationError(
                _('A person with this %s already exists') %
                (self.document_l10n.label, ))
Example #5
0
    def __init__(self, store, model=None, visual_mode=False):
        from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave
        is_new = not model
        self._sellable = None
        self._demo_mode = sysparam.get_bool('DEMO_MODE')
        self._requires_weighing_text = (
            "<b>%s</b>" % api.escape(_("This unit type requires weighing")))

        if self.ui_form_name:
            self.db_form = DatabaseForm(self.ui_form_name)
        else:
            self.db_form = None
        BaseEditor.__init__(self, store, model, visual_mode)
        self.enable_window_controls()
        if self._demo_mode:
            self._add_demo_warning()

        # Code suggestion. We need to do this before disabling sensitivity,
        # otherwise, the sellable will not be updated.
        if not self.code.read():
            self._update_default_sellable_code()
        edit_code_product = sysparam.get_bool('EDIT_CODE_PRODUCT')
        self.code.set_sensitive(not edit_code_product and not self.visual_mode)

        self.description.grab_focus()
        self.table.set_focus_chain([
            self.code,
            self.barcode,
            self.default_sale_cfop,
            self.description,
            self.cost_hbox,
            self.price_hbox,
            self.category_combo,
            self.tax_hbox,
            self.unit_combo,
        ])

        self._print_labels_btn = self.add_button('print_labels',
                                                 gtk.STOCK_PRINT)
        self._print_labels_btn.connect('clicked', self.on_print_labels_clicked,
                                       'print_labels')
        label = self._print_labels_btn.get_children()[0]
        label = label.get_children()[0].get_children()[1]
        label.set_label(_(u'Print labels'))

        self.setup_widgets()

        if not is_new and not self.visual_mode:
            # Although a sellable can be both removed/closed, we show only one,
            # to avoid having *lots* of buttons. If it's closed, provide a way
            # to reopen it, else, show a delete button if it can be removed
            # or a close button if it can be closed
            if self._sellable.is_closed():
                self._add_reopen_button()
            elif self._sellable.can_remove():
                self._add_delete_button()
            elif self._sellable.can_close():
                self._add_close_button()

        self.set_main_tab_label(self.model_name)
        price_slave = CategoryPriceSlave(self.store, self.model.sellable,
                                         self.visual_mode)
        self.add_extra_tab(_(u'Category Prices'), price_slave)
        self._setup_ui_forms()
        self._update_print_labels()
Example #6
0
class SellableEditor(BaseEditor):
    """This is a base class for ProductEditor and ServiceEditor and should
    be used when editing sellable objects. Note that sellable objects
    are instances inherited by Sellable."""

    # This must be be properly defined in the child classes
    model_name = None
    model_type = None

    gladefile = 'SellableEditor'
    confirm_widgets = ['description', 'cost', 'price']
    ui_form_name = None
    sellable_tax_widgets = ['tax_constant', 'tax_value']
    sellable_widgets = [
        'code', 'barcode', 'description', 'category_combo', 'cost', 'price',
        'status_str', 'default_sale_cfop', 'unit_combo'
    ]
    proxy_widgets = (sellable_tax_widgets + sellable_widgets)

    def __init__(self, store, model=None, visual_mode=False):
        from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave
        is_new = not model
        self._sellable = None
        self._demo_mode = sysparam.get_bool('DEMO_MODE')
        self._requires_weighing_text = (
            "<b>%s</b>" % api.escape(_("This unit type requires weighing")))

        if self.ui_form_name:
            self.db_form = DatabaseForm(self.ui_form_name)
        else:
            self.db_form = None
        BaseEditor.__init__(self, store, model, visual_mode)
        self.enable_window_controls()
        if self._demo_mode:
            self._add_demo_warning()

        # Code suggestion. We need to do this before disabling sensitivity,
        # otherwise, the sellable will not be updated.
        if not self.code.read():
            self._update_default_sellable_code()
        edit_code_product = sysparam.get_bool('EDIT_CODE_PRODUCT')
        self.code.set_sensitive(not edit_code_product and not self.visual_mode)

        self.description.grab_focus()
        self.table.set_focus_chain([
            self.code,
            self.barcode,
            self.default_sale_cfop,
            self.description,
            self.cost_hbox,
            self.price_hbox,
            self.category_combo,
            self.tax_hbox,
            self.unit_combo,
        ])

        self._print_labels_btn = self.add_button('print_labels',
                                                 gtk.STOCK_PRINT)
        self._print_labels_btn.connect('clicked', self.on_print_labels_clicked,
                                       'print_labels')
        label = self._print_labels_btn.get_children()[0]
        label = label.get_children()[0].get_children()[1]
        label.set_label(_(u'Print labels'))

        self.setup_widgets()

        if not is_new and not self.visual_mode:
            # Although a sellable can be both removed/closed, we show only one,
            # to avoid having *lots* of buttons. If it's closed, provide a way
            # to reopen it, else, show a delete button if it can be removed
            # or a close button if it can be closed
            if self._sellable.is_closed():
                self._add_reopen_button()
            elif self._sellable.can_remove():
                self._add_delete_button()
            elif self._sellable.can_close():
                self._add_close_button()

        self.set_main_tab_label(self.model_name)
        price_slave = CategoryPriceSlave(self.store, self.model.sellable,
                                         self.visual_mode)
        self.add_extra_tab(_(u'Category Prices'), price_slave)
        self._setup_ui_forms()
        self._update_print_labels()

    def _add_demo_warning(self):
        fmt = _("This is a demostration mode of Stoq, you cannot "
                "create more than %d products.\n"
                "To avoid this limitation, enable production mode.")
        self.set_message(fmt % (_DEMO_PRODUCT_LIMIT))
        if self.store.find(Sellable).count() > _DEMO_PRODUCT_LIMIT:
            self.disable_ok()

    def _add_extra_button(self,
                          label,
                          stock=None,
                          callback_func=None,
                          connect_on='clicked'):
        button = self.add_button(label, stock)
        if callback_func:
            button.connect(connect_on, callback_func, label)

    def _add_delete_button(self):
        self._add_extra_button(_('Remove'), gtk.STOCK_DELETE,
                               self._on_delete_button__clicked)

    def _add_close_button(self):
        if self._sellable.product:
            label = _('Close Product')
        else:
            label = _('Close Service')

        self._add_extra_button(label, None,
                               self._on_close_sellable_button__clicked)

    def _add_reopen_button(self):
        if self._sellable.product:
            label = _('Reopen Product')
        else:
            label = _('Reopen Service')

        self._add_extra_button(label, None,
                               self._on_reopen_sellable_button__clicked)

    def _update_default_sellable_code(self):
        code = Sellable.get_max_value(self.store, Sellable.code)
        self.code.update(next_value_for(code))

    def _update_print_labels(self):
        sellable = self.model.sellable
        self._print_labels_btn.set_sensitive(
            all([
                sellable.code, sellable.barcode, sellable.description,
                sellable.price
            ]))

    def _setup_ui_forms(self):
        if not self.db_form:
            return

        self.db_form.update_widget(self.code, other=self.code_lbl)
        self.db_form.update_widget(self.barcode, other=self.barcode_lbl)
        self.db_form.update_widget(self.category_combo,
                                   other=self.category_lbl)

    #
    #  Public API
    #

    def set_main_tab_label(self, tabname):
        self.sellable_notebook.set_tab_label(self.sellable_tab,
                                             gtk.Label(tabname))

    def add_extra_tab(self, tabname, tabslave):
        self.sellable_notebook.set_show_tabs(True)
        self.sellable_notebook.set_show_border(True)

        event_box = gtk.EventBox()
        event_box.show()
        self.sellable_notebook.append_page(event_box, gtk.Label(tabname))
        self.attach_slave(tabname, tabslave, event_box)

    def set_widget_formats(self):
        for widget in (self.cost, self.price):
            widget.set_adjustment(
                gtk.Adjustment(lower=0, upper=MAX_INT, step_incr=1))
        self.requires_weighing_label.set_size("small")
        self.requires_weighing_label.set_text("")

    def edit_sale_price(self):
        sellable = self.model.sellable
        self.store.savepoint('before_run_editor_sellable_price')
        result = run_dialog(SellablePriceEditor,
                            self.get_toplevel().get_toplevel(), self.store,
                            sellable)
        if result:
            self.sellable_proxy.update('price')
        else:
            self.store.rollback_to_savepoint(
                'before_run_editor_sellable_price')

    def setup_widgets(self):
        raise NotImplementedError

    def update_requires_weighing_label(self):
        unit = self._sellable.unit
        if unit and unit.unit_index == UnitType.WEIGHT:
            self.requires_weighing_label.set_text(self._requires_weighing_text)
        else:
            self.requires_weighing_label.set_text("")

    def _update_tax_value(self):
        if not hasattr(self, 'tax_proxy'):
            return
        self.tax_proxy.update('tax_constant.tax_value')

    def get_taxes(self):
        """Subclasses may override this method to provide a custom
        tax selection.

        :returns: a list of tuples containing the tax description and a
            :class:`stoqlib.domain.sellable.SellableTaxConstant` object.
        """
        return []

    def _fill_categories(self):
        categories = self.store.find(SellableCategory)
        self.category_combo.set_sensitive(
            any(categories) and not self.visual_mode)
        self.category_combo.prefill(
            api.for_combo(categories, attr='full_description'))

    #
    # BaseEditor hooks
    #

    def update_visual_mode(self):
        self.add_category.set_sensitive(False)
        self.sale_price_button.set_sensitive(False)

    def setup_sellable_combos(self):
        self._fill_categories()
        self.edit_category.set_sensitive(False)

        cfops = CfopData.get_for_sale(self.store)
        self.default_sale_cfop.prefill(api.for_combo(cfops, empty=''))

        self.setup_unit_combo()

    def setup_unit_combo(self):
        units = self.store.find(SellableUnit)
        self.unit_combo.prefill(api.for_combo(units, empty=_('No units')))

    def setup_tax_constants(self):
        taxes = self.get_taxes()
        self.tax_constant.prefill(taxes)

    def setup_proxies(self):
        self.set_widget_formats()
        self._sellable = self.model.sellable

        self.add_category.set_tooltip_text(_("Add a new category"))
        self.edit_category.set_tooltip_text(_("Edit the selected category"))
        self.setup_sellable_combos()
        self.setup_tax_constants()
        self.tax_proxy = self.add_proxy(self._sellable,
                                        SellableEditor.sellable_tax_widgets)

        self.sellable_proxy = self.add_proxy(self._sellable,
                                             SellableEditor.sellable_widgets)

        self.update_requires_weighing_label()

    def setup_slaves(self):
        from stoqlib.gui.slaves.sellableslave import SellableDetailsSlave
        details_slave = SellableDetailsSlave(self.store,
                                             self.model.sellable,
                                             visual_mode=self.visual_mode)
        self.attach_slave('slave_holder', details_slave)
        if isinstance(self.model, Product) and self.model.parent is not None:
            details_slave.notes.set_property('sensitive', False)

        # Make everything aligned by pytting notes_lbl on the same size group
        self.left_labels_group.add_widget(details_slave.notes_lbl)

    def _run_category_editor(self, category=None):
        self.store.savepoint('before_run_editor_sellable_category')
        model = run_dialog(SellableCategoryEditor, self, self.store, category)
        if model:
            self._fill_categories()
            self.category_combo.select(model)
        else:
            self.store.rollback_to_savepoint(
                'before_run_editor_sellable_category')

    #
    # Kiwi handlers
    #

    def _on_delete_button__clicked(self, button, parent_button_label=None):
        sellable_description = self._sellable.get_description()
        msg = (_("This will delete '%s' from the database. Are you sure?") %
               sellable_description)
        if not yesno(msg, gtk.RESPONSE_NO, _("Delete"), _("Keep")):
            return

        try:
            self._sellable.remove()
        except IntegrityError as details:
            warning(
                _("It was not possible to remove '%s'") % sellable_description,
                str(details))
            return

        # We are doing this by hand instead of calling confirm/cancel because,
        # if we call self.cancel(), the transaction will not be committed. If
        # we call self.confirm(), it will, but some on_confirm hooks (like
        # ProductComponentSlave's one) will try to create other objects and
        # relate them with this product that doesn't exist anymore (we removed
        # them above), resulting in an IntegrityError.
        self.retval = self.model
        self.store.retval = self.retval
        self.main_dialog.close()

    def _on_close_sellable_button__clicked(self,
                                           button,
                                           parent_button_label=None):
        msg = (_("Do you really want to close '%s'?\n"
                 "Please note that when it's closed, you won't be able to "
                 "commercialize it anymore.") %
               self._sellable.get_description())
        if not yesno(msg, gtk.RESPONSE_NO, parent_button_label,
                     _("Don't close")):
            return

        self._sellable.close()
        self.confirm()

    def _on_reopen_sellable_button__clicked(self,
                                            button,
                                            parent_button_label=None):
        msg = (_("Do you really want to reopen '%s'?\n"
                 "Note that when it's opened, you will be able to "
                 "commercialize it again.") % self._sellable.get_description())
        if not yesno(msg, gtk.RESPONSE_NO, parent_button_label,
                     _("Keep closed")):
            return

        self._sellable.set_available()
        self.confirm()

    def on_category_combo__content_changed(self, category):
        self.edit_category.set_sensitive(bool(category.get_selected()))

    def on_tax_constant__changed(self, combo):
        self._update_tax_value()

    def on_unit_combo__changed(self, combo):
        self.update_requires_weighing_label()

    def on_sale_price_button__clicked(self, button):
        self.edit_sale_price()

    def on_add_category__clicked(self, widget):
        self._run_category_editor()

    def on_edit_category__clicked(self, widget):
        self._run_category_editor(self.category_combo.get_selected())

    def on_code__validate(self, widget, value):
        if not value:
            return ValidationError(_(u'The code can not be empty.'))
        if self.model.sellable.check_code_exists(value):
            return ValidationError(_(u'The code %s already exists.') % value)

    def on_barcode__validate(self, widget, value):
        if not value:
            return
        if value and len(value) > 14:
            return ValidationError(_(u'Barcode must have 14 digits or less.'))
        if self.model.sellable.check_barcode_exists(value):
            return ValidationError(_('The barcode %s already exists') % value)
        if self._demo_mode and value not in _DEMO_BAR_CODES:
            return ValidationError(
                _("Cannot create new barcodes in "
                  "demonstration mode"))

    def on_price__validate(self, entry, value):
        if value <= 0:
            return ValidationError(_("Price cannot be zero or negative"))

    def on_cost__validate(self, entry, value):
        if value <= 0:
            return ValidationError(_("Cost cannot be zero or negative"))

    def after_description__changed(self, widget):
        self._update_print_labels()

    def after_code__changed(self, widget):
        self._update_print_labels()

    def after_barcode__changed(self, widget):
        self._update_print_labels()

    def after_price__changed(self, widget):
        self._update_print_labels()

    def on_print_labels_clicked(self, button, parent_label_button=None):
        label_data = run_dialog(PrintLabelEditor, None, self.store,
                                self.model.sellable)
        if label_data:
            print_labels(label_data, self.store)
Example #7
0
class _PersonEditorTemplate(BaseEditorSlave):
    model_type = Person
    gladefile = 'PersonEditorTemplate'

    proxy_widgets = ('name',
                     'phone_number',
                     'fax_number',
                     'mobile_number',
                     'email')

    def __init__(self, store, model, visual_mode, ui_form_name, parent):
        self._parent = parent
        if ui_form_name:
            self.db_form = DatabaseForm(store, ui_form_name)
        else:
            self.db_form = None

        super(_PersonEditorTemplate, self).__init__(store, model,
                                                    visual_mode=visual_mode)
        self._check_new_person()

    def _check_new_person(self):
        self.is_new_person = False
        # If this person is not in the default store, then it was created
        # inside another transaction that was not commited yet.
        default_store = api.get_default_store()
        if default_store.find(Person, id=self.model.id).is_empty():
            self.is_new_person = True

    #
    # BaseEditorSlave hooks
    #

    def create_model(self, store):
        return Person(name=u"", store=store)

    def setup_proxies(self):
        self._setup_widgets()
        self._setup_form_fields()
        self.proxy = self.add_proxy(self.model,
                                    _PersonEditorTemplate.proxy_widgets)

    def setup_slaves(self):
        self.address_slave = AddressSlave(
            self.store, self.model, self.model.get_main_address(),
            visual_mode=self.visual_mode,
            db_form=self.db_form)
        self.attach_slave('address_holder', self.address_slave)
        self.attach_model_slave('note_holder', NoteSlave, self.model)

    def on_confirm(self):
        main_address = self.address_slave.model
        main_address.person = self.model

    #
    # Public API
    #

    def add_extra_tab(self, tab_label, slave, position=None):
        """Adds an extra tab to the editor

        :param tab_label: the label that will be display on the tab
        :param slave: the slave that will be attached to the new tab
        :param position: the position the tab will be attached
        """
        event_box = gtk.EventBox()
        self.person_notebook.append_page(event_box, gtk.Label(tab_label))
        self.attach_slave(tab_label, slave, event_box)
        event_box.show()

        if position is not None:
            self.person_notebook.reorder_child(event_box, position)
            self.person_notebook.set_current_page(position)

    def attach_role_slave(self, slave):
        self.attach_slave('role_holder', slave)

    def attach_model_slave(self, name, slave_type, slave_model):
        slave = slave_type(self.store, slave_model,
                           visual_mode=self.visual_mode)
        self.attach_slave(name, slave)
        return slave

    #
    # Kiwi handlers
    #

    def on_name__map(self, entry):
        self.name.grab_focus()

    def on_address_button__clicked(self, button):
        main_address = self.model.get_main_address()
        if not main_address.is_valid_model():
            msg = _(u"You must define a valid main address before\n"
                    "adding additional addresses")
            warning(msg)
            return

        result = run_dialog(AddressAdditionDialog, self._parent,
                            self.store, person=self.model,
                            reuse_store=self.is_new_person)
        if not result:
            return

        new_main_address = self.model.get_main_address()
        if new_main_address is not main_address:
            self.address_slave.set_model(new_main_address)

    def on_contact_info_button__clicked(self, button):
        run_dialog(ContactInfoListDialog, self._parent, self.store,
                   person=self.model, reuse_store=self.is_new_person)

    def on_calls_button__clicked(self, button):
        run_dialog(CallsSearch, self._parent, self.store,
                   person=self.model, reuse_store=self.is_new_person)

    def on_credit_check_history_button__clicked(self, button):
        run_dialog(CreditCheckHistorySearch, self._parent, self.store,
                   client=self.model.client, reuse_store=self.is_new_person)

    #
    # Private API
    #

    def _setup_widgets(self):
        individual = self.model.individual
        company = self.model.company
        if not (individual or company):
            raise DatabaseInconsistency('A person must have at least a '
                                        'company or an individual set.')
        tab_child = self.person_data_tab
        if individual and company:
            tab_text = _('Individual/Company Data')
            self.company_frame.set_label(_('Company Data'))
            self.company_frame.show()
            self.individual_frame.set_label(_('Individual Data'))
            self.individual_frame.show()
        elif individual:
            tab_text = _('Individual Data')
            self.company_frame.hide()
            self.individual_frame.set_label('')
            self.individual_frame.show()
        else:
            tab_text = _('Company Data')
            self.individual_frame.hide()
            self.company_frame.set_label('')
            self.company_frame.show()
        self.person_notebook.set_tab_label_text(tab_child, tab_text)
        addresses = self.model.get_total_addresses()
        if addresses == 2:
            self.address_button.set_label(_("1 More Address..."))
        elif addresses > 2:
            self.address_button.set_label(_("%i More Addresses...")
                                          % (addresses - 1))
        if not self.model.client:
            self.credit_check_history_button.hide()

    def _setup_form_fields(self):
        if not self.db_form:
            return
        self.db_form.update_widget(self.name,
                                   other=self.name_lbl)
        self.db_form.update_widget(self.phone_number,
                                   other=self.phone_number_lbl)
        self.db_form.update_widget(self.fax_number, u'fax',
                                   other=self.fax_lbl)
        self.db_form.update_widget(self.email,
                                   other=self.email_lbl)
        self.db_form.update_widget(self.mobile_number,
                                   other=self.mobile_lbl)
Example #8
0
    def __init__(self, store, model=None, visual_mode=False):
        from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave
        is_new = not model
        self._sellable = None
        self._demo_mode = sysparam.get_bool('DEMO_MODE')
        self._requires_weighing_text = (
            "<b>%s</b>" % api.escape(_("This unit type requires weighing")))

        if self.ui_form_name:
            self.db_form = DatabaseForm(store, self.ui_form_name)
        else:
            self.db_form = None
        BaseEditor.__init__(self, store, model, visual_mode)
        self.enable_window_controls()
        if self._demo_mode:
            self._add_demo_warning()

        # code suggestion
        edit_code_product = sysparam.get_bool('EDIT_CODE_PRODUCT')
        self.code.set_sensitive(not edit_code_product and not self.visual_mode)
        if not self.code.read():
            self._update_default_sellable_code()

        self.description.grab_focus()
        self.table.set_focus_chain([self.code,
                                    self.barcode,
                                    self.default_sale_cfop,
                                    self.description,
                                    self.cost_hbox,
                                    self.price_hbox,
                                    self.category_combo,
                                    self.tax_hbox,
                                    self.unit_combo,
                                    ])

        print_labels_button = self.add_button('print_labels', gtk.STOCK_PRINT)
        print_labels_button.connect('clicked', self.on_print_labels_clicked,
                                    'print_labels')
        label = print_labels_button.get_children()[0]
        label = label.get_children()[0].get_children()[1]
        label.set_label(_(u'Print labels'))

        self.setup_widgets()

        if not is_new and not self.visual_mode:
            # Although a sellable can be both removed/closed, we show only one,
            # to avoid having *lots* of buttons. If it's closed, provide a way
            # to reopen it, else, show a delete button if it can be removed
            # or a close button if it can be closed
            if self._sellable.is_closed():
                self._add_reopen_button()
            elif self._sellable.can_remove():
                self._add_delete_button()
            elif self._sellable.can_close():
                self._add_close_button()

        self.set_main_tab_label(self.model_name)
        price_slave = CategoryPriceSlave(self.store, self.model.sellable,
                                         self.visual_mode)
        self.add_extra_tab(_(u'Category Prices'), price_slave)
        self._setup_ui_forms()
Example #9
0
class SellableEditor(BaseEditor):
    """This is a base class for ProductEditor and ServiceEditor and should
    be used when editing sellable objects. Note that sellable objects
    are instances inherited by Sellable."""

    # This must be be properly defined in the child classes
    model_name = None
    model_type = None

    gladefile = 'SellableEditor'
    confirm_widgets = ['description', 'cost', 'price']
    ui_form_name = None
    sellable_tax_widgets = ['tax_constant', 'tax_value']
    sellable_widgets = ['code',
                        'barcode',
                        'description',
                        'category_combo',
                        'cost',
                        'price',
                        'status_str',
                        'default_sale_cfop',
                        'unit_combo']
    proxy_widgets = (sellable_tax_widgets + sellable_widgets)

    def __init__(self, store, model=None, visual_mode=False):
        from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave
        is_new = not model
        self._sellable = None
        self._demo_mode = sysparam.get_bool('DEMO_MODE')
        self._requires_weighing_text = (
            "<b>%s</b>" % api.escape(_("This unit type requires weighing")))

        if self.ui_form_name:
            self.db_form = DatabaseForm(store, self.ui_form_name)
        else:
            self.db_form = None
        BaseEditor.__init__(self, store, model, visual_mode)
        self.enable_window_controls()
        if self._demo_mode:
            self._add_demo_warning()

        # code suggestion
        edit_code_product = sysparam.get_bool('EDIT_CODE_PRODUCT')
        self.code.set_sensitive(not edit_code_product and not self.visual_mode)
        if not self.code.read():
            self._update_default_sellable_code()

        self.description.grab_focus()
        self.table.set_focus_chain([self.code,
                                    self.barcode,
                                    self.default_sale_cfop,
                                    self.description,
                                    self.cost_hbox,
                                    self.price_hbox,
                                    self.category_combo,
                                    self.tax_hbox,
                                    self.unit_combo,
                                    ])

        print_labels_button = self.add_button('print_labels', gtk.STOCK_PRINT)
        print_labels_button.connect('clicked', self.on_print_labels_clicked,
                                    'print_labels')
        label = print_labels_button.get_children()[0]
        label = label.get_children()[0].get_children()[1]
        label.set_label(_(u'Print labels'))

        self.setup_widgets()

        if not is_new and not self.visual_mode:
            # Although a sellable can be both removed/closed, we show only one,
            # to avoid having *lots* of buttons. If it's closed, provide a way
            # to reopen it, else, show a delete button if it can be removed
            # or a close button if it can be closed
            if self._sellable.is_closed():
                self._add_reopen_button()
            elif self._sellable.can_remove():
                self._add_delete_button()
            elif self._sellable.can_close():
                self._add_close_button()

        self.set_main_tab_label(self.model_name)
        price_slave = CategoryPriceSlave(self.store, self.model.sellable,
                                         self.visual_mode)
        self.add_extra_tab(_(u'Category Prices'), price_slave)
        self._setup_ui_forms()

    def _add_demo_warning(self):
        fmt = _("This is a demostration mode of Stoq, you cannot "
                "create more than %d products.\n"
                "To avoid this limitation, enable production mode.")
        self.set_message(fmt % (_DEMO_PRODUCT_LIMIT))
        if self.store.find(Sellable).count() > _DEMO_PRODUCT_LIMIT:
            self.disable_ok()

    def _add_extra_button(self, label, stock=None,
                          callback_func=None, connect_on='clicked'):
        button = self.add_button(label, stock)
        if callback_func:
            button.connect(connect_on, callback_func, label)

    def _add_delete_button(self):
        self._add_extra_button(_('Remove'), gtk.STOCK_DELETE,
                               self._on_delete_button__clicked)

    def _add_close_button(self):
        if self._sellable.product:
            label = _('Close Product')
        else:
            label = _('Close Service')

        self._add_extra_button(label, None,
                               self._on_close_sellable_button__clicked)

    def _add_reopen_button(self):
        if self._sellable.product:
            label = _('Reopen Product')
        else:
            label = _('Reopen Service')

        self._add_extra_button(label, None,
                               self._on_reopen_sellable_button__clicked)

    def _update_default_sellable_code(self):
        code = Sellable.get_max_value(self.store, Sellable.code)
        self.code.update(next_value_for(code))

    def _setup_ui_forms(self):
        if not self.db_form:
            return

        self.db_form.update_widget(self.code, other=self.code_lbl)
        self.db_form.update_widget(self.barcode, other=self.barcode_lbl)
        self.db_form.update_widget(self.category_combo,
                                   other=self.category_lbl)

    #
    #  Public API
    #

    def set_main_tab_label(self, tabname):
        self.sellable_notebook.set_tab_label(self.sellable_tab,
                                             gtk.Label(tabname))

    def add_extra_tab(self, tabname, tabslave):
        self.sellable_notebook.set_show_tabs(True)
        self.sellable_notebook.set_show_border(True)

        event_box = gtk.EventBox()
        event_box.show()
        self.sellable_notebook.append_page(event_box, gtk.Label(tabname))
        self.attach_slave(tabname, tabslave, event_box)

    def set_widget_formats(self):
        for widget in (self.cost, self.price):
            widget.set_adjustment(gtk.Adjustment(lower=0, upper=MAX_INT,
                                                 step_incr=1))
        self.requires_weighing_label.set_size("small")
        self.requires_weighing_label.set_text("")

    def edit_sale_price(self):
        sellable = self.model.sellable
        self.store.savepoint('before_run_editor_sellable_price')
        result = run_dialog(SellablePriceEditor, self, self.store, sellable)
        if result:
            self.sellable_proxy.update('price')
        else:
            self.store.rollback_to_savepoint('before_run_editor_sellable_price')

    def setup_widgets(self):
        raise NotImplementedError

    def update_requires_weighing_label(self):
        unit = self._sellable.unit
        if unit and unit.unit_index == UnitType.WEIGHT:
            self.requires_weighing_label.set_text(self._requires_weighing_text)
        else:
            self.requires_weighing_label.set_text("")

    def _update_tax_value(self):
        if not hasattr(self, 'tax_proxy'):
            return
        self.tax_proxy.update('tax_constant.tax_value')

    def get_taxes(self):
        """Subclasses may override this method to provide a custom
        tax selection.

        :returns: a list of tuples containing the tax description and a
            :class:`stoqlib.domain.sellable.SellableTaxConstant` object.
        """
        return []

    def _fill_categories(self):
        categories = self.store.find(SellableCategory)
        self.category_combo.prefill(api.for_combo(categories,
                                                  attr='full_description'))

    #
    # BaseEditor hooks
    #

    def update_visual_mode(self):
        self.add_category.set_sensitive(False)
        self.sale_price_button.set_sensitive(False)

    def setup_sellable_combos(self):
        self._fill_categories()
        self.edit_category.set_sensitive(False)

        cfops = self.store.find(CfopData)
        self.default_sale_cfop.prefill(api.for_combo(cfops, empty=''))

        self.setup_unit_combo()

    def setup_unit_combo(self):
        units = self.store.find(SellableUnit)
        self.unit_combo.prefill(api.for_combo(units, empty=_('No units')))

    def setup_tax_constants(self):
        taxes = self.get_taxes()
        self.tax_constant.prefill(taxes)

    def setup_proxies(self):
        self.set_widget_formats()
        self._sellable = self.model.sellable

        self.add_category.set_tooltip_text(_("Add a new category"))
        self.edit_category.set_tooltip_text(_("Edit the selected category"))
        self.setup_sellable_combos()
        self.setup_tax_constants()
        self.tax_proxy = self.add_proxy(self._sellable,
                                        SellableEditor.sellable_tax_widgets)

        self.sellable_proxy = self.add_proxy(self._sellable,
                                             SellableEditor.sellable_widgets)

        self.update_requires_weighing_label()

    def setup_slaves(self):
        from stoqlib.gui.slaves.sellableslave import SellableDetailsSlave
        details_slave = SellableDetailsSlave(self.store, self.model.sellable,
                                             visual_mode=self.visual_mode)
        self.attach_slave('slave_holder', details_slave)

        # Make everything aligned by pytting notes_lbl on the same size group
        self.left_labels_group.add_widget(details_slave.notes_lbl)

    def _run_category_editor(self, category=None):
        self.store.savepoint('before_run_editor_sellable_category')
        model = run_dialog(SellableCategoryEditor, self, self.store, category)
        if model:
            self._fill_categories()
            self.category_combo.select(model)
        else:
            self.store.rollback_to_savepoint('before_run_editor_sellable_category')

    #
    # Kiwi handlers
    #

    def _on_delete_button__clicked(self, button, parent_button_label=None):
        sellable_description = self._sellable.get_description()
        msg = (_("This will delete '%s' from the database. Are you sure?")
               % sellable_description)
        if not yesno(msg, gtk.RESPONSE_NO, _("Delete"), _("Keep")):
            return

        try:
            self._sellable.remove()
        except IntegrityError as details:
            warning(_("It was not possible to remove '%s'")
                    % sellable_description, str(details))
            return

        # We are doing this by hand instead of calling confirm/cancel because,
        # if we call self.cancel(), the transaction will not be committed. If
        # we call self.confirm(), it will, but some on_confirm hooks (like
        # ProductComponentSlave's one) will try to create other objects and
        # relate them with this product that doesn't exist anymore (we removed
        # them above), resulting in an IntegrityError.
        self.retval = self.model
        self.store.retval = self.retval
        self.main_dialog.close()

    def _on_close_sellable_button__clicked(self, button,
                                           parent_button_label=None):
        msg = (_("Do you really want to close '%s'?\n"
                 "Please note that when it's closed, you won't be able to "
                 "commercialize it anymore.")
               % self._sellable.get_description())
        if not yesno(msg, gtk.RESPONSE_NO,
                     parent_button_label, _("Don't close")):
            return

        self._sellable.close()
        self.confirm()

    def _on_reopen_sellable_button__clicked(self, button,
                                            parent_button_label=None):
        msg = (_("Do you really want to reopen '%s'?\n"
                 "Note that when it's opened, you will be able to "
                 "commercialize it again.") % self._sellable.get_description())
        if not yesno(msg, gtk.RESPONSE_NO,
                     parent_button_label, _("Keep closed")):
            return

        self._sellable.set_available()
        self.confirm()

    def on_category_combo__content_changed(self, category):
        self.edit_category.set_sensitive(bool(category.get_selected()))

    def on_tax_constant__changed(self, combo):
        self._update_tax_value()

    def on_unit_combo__changed(self, combo):
        self.update_requires_weighing_label()

    def on_sale_price_button__clicked(self, button):
        self.edit_sale_price()

    def on_add_category__clicked(self, widget):
        self._run_category_editor()

    def on_edit_category__clicked(self, widget):
        self._run_category_editor(self.category_combo.get_selected())

    def on_code__validate(self, widget, value):
        if not value:
            return ValidationError(_(u'The code can not be empty.'))
        if self.model.sellable.check_code_exists(value):
            return ValidationError(_(u'The code %s already exists.') % value)

    def on_barcode__validate(self, widget, value):
        if not value:
            return
        if value and len(value) > 14:
            return ValidationError(_(u'Barcode must have 14 digits or less.'))
        if self.model.sellable.check_barcode_exists(value):
            return ValidationError(_('The barcode %s already exists') % value)
        if self._demo_mode and value not in _DEMO_BAR_CODES:
            return ValidationError(_("Cannot create new barcodes in "
                                     "demonstration mode"))

    def on_price__validate(self, entry, value):
        if value <= 0:
            return ValidationError(_("Price cannot be zero or negative"))

    def on_cost__validate(self, entry, value):
        if value <= 0:
            return ValidationError(_("Cost cannot be zero or negative"))

    def on_print_labels_clicked(self, button, parent_label_button=None):
        label_data = run_dialog(PrintLabelEditor, None, self.store,
                                self.model.sellable)
        if label_data:
            print_labels(label_data, self.store)
Example #10
0
class _PersonEditorTemplate(BaseEditorSlave):
    model_type = Person
    gladefile = 'PersonEditorTemplate'

    proxy_widgets = ('name', 'phone_number', 'fax_number', 'mobile_number',
                     'email')

    def __init__(self, store, model, visual_mode, ui_form_name, parent):
        self._parent = parent
        if ui_form_name:
            self.db_form = DatabaseForm(ui_form_name)
        else:
            self.db_form = None

        super(_PersonEditorTemplate, self).__init__(store,
                                                    model,
                                                    visual_mode=visual_mode)

    #
    # BaseEditorSlave hooks
    #

    def create_model(self, store):
        return Person(name=u"", store=store)

    def setup_proxies(self):
        self._setup_widgets()
        self._setup_form_fields()
        self.proxy = self.add_proxy(self.model,
                                    _PersonEditorTemplate.proxy_widgets)

    def setup_slaves(self):
        self.address_slave = AddressSlave(self.store,
                                          self.model,
                                          self.model.get_main_address(),
                                          visual_mode=self.visual_mode,
                                          db_form=self.db_form)
        self.attach_slave('address_holder', self.address_slave)
        self.attach_model_slave('note_holder', NoteSlave, self.model)

    def on_confirm(self):
        main_address = self.address_slave.model
        main_address.person = self.model

    #
    # Public API
    #

    def add_extra_tab(self, tab_label, slave, position=None):
        """Adds an extra tab to the editor

        :param tab_label: the label that will be display on the tab
        :param slave: the slave that will be attached to the new tab
        :param position: the position the tab will be attached
        """
        event_box = gtk.EventBox()
        self.person_notebook.append_page(event_box, gtk.Label(tab_label))
        self.attach_slave(tab_label, slave, event_box)
        event_box.show()

        if position is not None:
            self.person_notebook.reorder_child(event_box, position)
            self.person_notebook.set_current_page(position)

    def attach_role_slave(self, slave):
        self.attach_slave('role_holder', slave)

    def attach_model_slave(self, name, slave_type, slave_model):
        slave = slave_type(self.store,
                           slave_model,
                           visual_mode=self.visual_mode)
        self.attach_slave(name, slave)
        return slave

    #
    # Kiwi handlers
    #

    def on_name__map(self, entry):
        self.name.grab_focus()

    def on_address_button__clicked(self, button):
        main_address = self.model.get_main_address()
        if not main_address.is_valid_model():
            msg = _(u"You must define a valid main address before\n"
                    "adding additional addresses")
            warning(msg)
            return

        result = run_dialog(AddressAdditionDialog,
                            self._parent,
                            self.store,
                            person=self.model,
                            reuse_store=not self.visual_mode)
        if not result:
            return

        new_main_address = self.model.get_main_address()
        if new_main_address is not main_address:
            self.address_slave.set_model(new_main_address)

    def on_contact_info_button__clicked(self, button):
        run_dialog(ContactInfoListDialog,
                   self._parent,
                   self.store,
                   person=self.model,
                   reuse_store=not self.visual_mode)

    def on_calls_button__clicked(self, button):
        run_dialog(CallsSearch,
                   self._parent,
                   self.store,
                   person=self.model,
                   reuse_store=not self.visual_mode)

    def on_credit_check_history_button__clicked(self, button):
        run_dialog(CreditCheckHistorySearch,
                   self._parent,
                   self.store,
                   client=self.model.client,
                   reuse_store=not self.visual_mode)

    #
    # Private API
    #

    def _setup_widgets(self):
        individual = self.model.individual
        company = self.model.company
        if not (individual or company):
            raise DatabaseInconsistency('A person must have at least a '
                                        'company or an individual set.')
        tab_child = self.person_data_tab
        if individual and company:
            tab_text = _('Individual/Company Data')
            self.company_frame.set_label(_('Company Data'))
            self.company_frame.show()
            self.individual_frame.set_label(_('Individual Data'))
            self.individual_frame.show()
        elif individual:
            tab_text = _('Individual Data')
            self.company_frame.hide()
            label_widget = self.individual_frame.get_label_widget()
            if label_widget is not None:
                label_widget.hide()
            self.individual_frame.show()
        else:
            tab_text = _('Company Data')
            self.individual_frame.hide()
            label_widget = self.company_frame.get_label_widget()
            if label_widget is not None:
                label_widget.hide()
            self.company_frame.show()
        self.person_notebook.set_tab_label_text(tab_child, tab_text)
        addresses = self.model.get_total_addresses()
        if addresses == 2:
            self.address_button.set_label(_("1 More Address..."))
        elif addresses > 2:
            self.address_button.set_label(
                _("%i More Addresses...") % (addresses - 1))
        if not self.model.client:
            self.credit_check_history_button.hide()

    def _setup_form_fields(self):
        if not self.db_form:
            return
        self.db_form.update_widget(self.name, other=self.name_lbl)
        self.db_form.update_widget(self.phone_number,
                                   other=self.phone_number_lbl)
        self.db_form.update_widget(self.fax_number, u'fax', other=self.fax_lbl)
        self.db_form.update_widget(self.email, other=self.email_lbl)
        self.db_form.update_widget(self.mobile_number, other=self.mobile_lbl)
Example #11
0
 def __init__(self, store, model, visual_mode=None, ui_form_name=None):
     self.db_form = DatabaseForm(ui_form_name) if ui_form_name else None
     BaseEditorSlave.__init__(self, store, model, visual_mode=visual_mode)
Example #12
0
 def __init__(self, store, model, visual_mode=None, ui_form_name=None):
     self.db_form = DatabaseForm(ui_form_name) if ui_form_name else None
     BaseEditorSlave.__init__(self, store, model, visual_mode=visual_mode)