示例#1
0
 def _setup_clients_widget(self):
     marker('Filling clients')
     self.client_gadget = ClientEntryGadget(
         entry=self.client,
         store=self.store,
         initial_value=self.model.client,
         parent=self.wizard,
         run_editor=self._run_client_editor)
     marker('Filled clients')
示例#2
0
 def _setup_clients_widget(self):
     self.client.mandatory = True
     self.client_gadget = ClientEntryGadget(
         entry=self.client,
         store=self.store,
         initial_value=self.model.client,
         parent=self.wizard)
示例#3
0
 def _setup_clients_widget(self):
     marker('Filling clients')
     self.client_gadget = ClientEntryGadget(
         entry=self.client,
         store=self.store,
         initial_value=self.model.client,
         parent=self.wizard,
         run_editor=self._run_client_editor)
     marker('Filled clients')
示例#4
0
class StartSaleQuoteStep(WizardEditorStep):
    gladefile = 'SalesPersonQuoteWizardStep'
    model_type = Sale
    sale_widgets = ['client', 'salesperson', 'expire_date',
                    'client_category']
    invoice_widgets = ['operation_nature']
    cfop_widgets = ['cfop']
    proxy_widgets = sale_widgets + invoice_widgets + cfop_widgets

    def _setup_widgets(self):
        # Salesperson combo
        salespersons = SalesPerson.get_active_salespersons(self.store)
        self.salesperson.prefill(salespersons)

        change_salesperson = sysparam.get_int('ACCEPT_CHANGE_SALESPERSON')
        if change_salesperson == ChangeSalespersonPolicy.ALLOW:
            self.salesperson.grab_focus()
        elif change_salesperson == ChangeSalespersonPolicy.DISALLOW:
            self.salesperson.set_sensitive(False)
        elif change_salesperson == ChangeSalespersonPolicy.FORCE_CHOOSE:
            self.model.salesperson = None
            self.salesperson.grab_focus()
        else:
            raise AssertionError

        # CFOP combo
        if sysparam.get_bool('ASK_SALES_CFOP'):
            cfops = CfopData.get_for_sale(self.store)
            self.cfop.prefill(api.for_combo(cfops))
        else:
            self.cfop_lbl.hide()
            self.cfop.hide()
            self.create_cfop.hide()

        self._fill_clients_category_combo()
        self._setup_clients_widget()

        self._client_credit_set_visible(bool(self.client.read()))

    def _client_credit_set_visible(self, visible):
        if visible and self.model.client:
            self.client_credit.set_text(
                self.model.client.credit_account_balance.format(precision=2)
            )
        self.client_credit.set_visible(visible)
        self.client_credit_lbl.set_visible(visible)

    def _setup_clients_widget(self):
        self.client_gadget = ClientEntryGadget(
            entry=self.client,
            store=self.store,
            initial_value=self.model.client,
            parent=self.wizard)

    def _fill_clients_category_combo(self):
        categories = self.store.find(ClientCategory).order_by(ClientCategory.name)
        self.client_category.prefill(api.for_combo(categories, empty=''))

        if categories.is_empty():
            self.client_category.hide()
            self.client_category_lbl.hide()

    def post_init(self):
        self.toogle_client_details()
        self.register_validate_function(self.wizard.refresh_next)
        self.force_validation()

    def next_step(self):
        return SaleQuoteItemStep(self.wizard, self, self.store, self.model)

    def has_previous_step(self):
        return False

    def setup_proxies(self):
        self._setup_widgets()
        self.sale_proxy = self.add_proxy(self.model,
                                         StartSaleQuoteStep.sale_widgets)
        self.invoice_proxy = self.add_proxy(self.model.invoice,
                                            StartSaleQuoteStep.invoice_widgets)
        if sysparam.get_bool('ASK_SALES_CFOP'):
            self.add_proxy(self.model, StartSaleQuoteStep.cfop_widgets)

        expire_delta = sysparam.get_int('EXPIRATION_SALE_QUOTE_DATE')
        if expire_delta > 0 and not self.model.expire_date:
            # Only set the expire date if id doesn't already have one.
            self.expire_date.update(localtoday() +
                                    relativedelta(days=expire_delta))

    def toogle_client_details(self):
        client = self.model.client

        if client and client.status != Client.STATUS_SOLVENT:
            self.client_gadget.update_edit_button(
                gtk.STOCK_DIALOG_WARNING, _("The client is not solvent"))

    #
    #   Callbacks
    #

    def after_client__content_changed(self, widget):
        # During the setup of client_gadget, the client is changed. So this method is
        # called before the client_gadget be completely created.
        # Check if the client_gadget was created to continue setting other widgets.
        if not hasattr(self, 'client_gadget'):
            return

        client = self.model.client
        self.toogle_client_details()
        self._client_credit_set_visible(bool(client))
        if not client:
            return
        self.client_category.select(client.category)

    def on_expire_date__validate(self, widget, value):
        # open_date has a seconds precision, so that why we are rounding it to
        # date here.
        if value.date() < self.model.open_date.date():
            msg = _(u"The expire date must be after the sale open date")
            return ValidationError(msg)

    def on_notes_button__clicked(self, *args):
        self.store.savepoint('before_run_notes_editor')

        model = self.model.comments.first()
        if not model:
            model = SaleComment(store=self.store, sale=self.model,
                                author=api.get_current_user(self.store))
        rv = run_dialog(NoteEditor, self.wizard, self.store, model, 'comment',
                        title=_('Additional Information'))
        if not rv:
            self.store.rollback_to_savepoint('before_run_notes_editor')

    def on_create_cfop__clicked(self, widget):
        self.store.savepoint('before_run_editor_cfop')
        cfop = run_dialog(CfopEditor, self.wizard, self.store, None)
        if cfop:
            self.cfop.append_item(cfop.get_description(), cfop)
            self.cfop.select_item_by_data(cfop)
        else:
            self.store.rollback_to_savepoint('before_run_editor_cfop')

    def on_client__validate(self, widget, client):
        return StockOperationPersonValidationEvent.emit(client.person, type(client))
示例#5
0
 def _setup_clients_widget(self):
     self.client_gadget = ClientEntryGadget(
         entry=self.client,
         store=self.store,
         initial_value=self.model.client,
         parent=self.wizard)
示例#6
0
class SalesPersonStep(BaseMethodSelectionStep, WizardEditorStep):
    """ An abstract step which allows to define a salesperson, the sale's
    discount and surcharge, when it is needed.
    """
    gladefile = 'SalesPersonStep'
    model_type = Sale
    proxy_widgets = ['salesperson', 'client', 'transporter', 'cost_center']
    cfop_widgets = ('cfop', )

    def __init__(self, wizard, store, model, payment_group, previous=None):
        self.pm_slave = None
        self.payment_group = payment_group

        BaseMethodSelectionStep.__init__(self)
        marker("WizardEditorStep.__init__")
        WizardEditorStep.__init__(self,
                                  store,
                                  wizard,
                                  model,
                                  previous=previous)

        self._update_totals()
        self.update_discount_and_surcharge()

    #
    # Private API
    #

    def _update_totals(self):
        subtotal = self.wizard.get_subtotal()
        self.subtotal_lbl.update(subtotal)

        total_paid = self.wizard.get_total_paid()
        self.total_paid_lbl.update(total_paid)

        to_pay = self.model.get_total_sale_amount(
            subtotal=subtotal) - total_paid
        self.cash_change_slave.update_total_sale_amount(to_pay)
        self.total_lbl.update(to_pay)

    def _setup_clients_widget(self):
        marker('Filling clients')
        self.client_gadget = ClientEntryGadget(
            entry=self.client,
            store=self.store,
            initial_value=self.model.client,
            parent=self.wizard,
            run_editor=self._run_client_editor)
        marker('Filled clients')

    def _run_client_editor(self,
                           store,
                           model,
                           description=None,
                           visual_mode=False):
        return run_person_role_dialog(ClientEditor,
                                      self.wizard,
                                      store,
                                      model,
                                      document=self.wizard._current_document,
                                      description=description,
                                      visual_mode=visual_mode)

    def _fill_transporter_combo(self):
        marker('Filling transporters')
        transporters = Transporter.get_active_transporters(self.store)
        items = api.for_person_combo(transporters)
        self.transporter.prefill(items)
        self.transporter.set_sensitive(len(items))
        marker('Filled transporters')

    def _fill_cost_center_combo(self):
        marker('Filling cost centers')
        cost_centers = CostCenter.get_active(self.store)

        # we keep this value because each call to is_empty() is a new sql query
        # to the database
        cost_centers_exists = not cost_centers.is_empty()

        if cost_centers_exists:
            self.cost_center.prefill(
                api.for_combo(cost_centers,
                              attr='name',
                              empty=_('No cost center.')))
        self.cost_center.set_visible(cost_centers_exists)
        self.cost_center_lbl.set_visible(cost_centers_exists)
        marker('Filled cost centers')

    def _fill_cfop_combo(self):
        marker('Filling CFOPs')
        cfops = CfopData.get_for_sale(self.store)
        self.cfop.prefill(api.for_combo(cfops))
        marker('Filled CFOPs')

    #
    # Public API
    #

    def update_discount_and_surcharge(self):
        marker("update_discount_and_surcharge")
        # Here we need avoid to reset sale data defined when creating the
        # Sale in the POS application, i.e, we should not reset the
        # discount and surcharge if they are already set (this is the
        # case when one of the parameters, CONFIRM_SALES_ON_TILL or
        # USE_TRADE_AS_DISCOUNT is enabled).
        if (not sysparam.get_bool('CONFIRM_SALES_ON_TILL')
                and not sysparam.get_bool('USE_TRADE_AS_DISCOUNT')):
            self.model.discount_value = currency(0)
            self.model.surcharge_value = currency(0)

    def setup_widgets(self):
        marker('Setting up widgets')
        # Only quotes have expire date.
        self.expire_date.hide()
        self.expire_label.hide()

        # Hide client category widgets
        self.client_category_lbl.hide()
        self.client_category.hide()

        # if the NF-e plugin is active, the client is mandantory in this
        # wizard (in this situation, we have only quote sales).
        if self.model.status == Sale.STATUS_QUOTE:
            manager = get_plugin_manager()
            mandatory_client = manager.is_active('nfe')
            self.client.set_property('mandatory', mandatory_client)

        marker('Filling sales persons')
        salespersons = SalesPerson.get_active_salespersons(self.store)
        self.salesperson.prefill(salespersons)
        marker('Finished filling sales persons')

        marker('Read parameter')
        change_salesperson = sysparam.get_int('ACCEPT_CHANGE_SALESPERSON')
        if change_salesperson == ChangeSalespersonPolicy.ALLOW:
            self.salesperson.grab_focus()
        elif change_salesperson == ChangeSalespersonPolicy.DISALLOW:
            self.salesperson.set_sensitive(False)
        elif change_salesperson == ChangeSalespersonPolicy.FORCE_CHOOSE:
            self.model.salesperson = None
            self.salesperson.grab_focus()
        else:
            raise AssertionError
        marker('Finished reading parameter')
        self._setup_clients_widget()
        self._fill_transporter_combo()
        self._fill_cost_center_combo()

        if sysparam.get_bool('ASK_SALES_CFOP'):
            self._fill_cfop_combo()
        else:
            self.cfop_lbl.hide()
            self.cfop.hide()
            self.create_cfop.hide()

        marker('Finished setting up widgets')

    def _refresh_next(self, validation_value):
        self.client.validate(force=True)
        client_valid = self.client.is_valid()
        self.wizard.refresh_next(validation_value and client_valid)

    #
    # WizardStep hooks
    #

    def post_init(self):
        BaseMethodSelectionStep.post_init(self)

        marker('Entering post_init')
        if self.wizard.need_create_payment():
            self.wizard.payment_group.clear_unused()
        self.register_validate_function(self._refresh_next)
        self._update_next_step(self.get_selected_method())
        # If there's no salesperson, keep the focus there as it should be
        # selected first to have a nice flow
        if (hasattr(self, 'cash_change_slave')
                and self.model.salesperson is not None):
            self.cash_change_slave.received_value.grab_focus()

        self.force_validation()
        marker('Leaving post_init')

    def setup_slaves(self):
        marker('Setting up slaves')
        BaseMethodSelectionStep.setup_slaves(self)
        marker('Finished parent')

        self.pm_slave.set_client(self.model.client,
                                 total_amount=self.wizard.get_total_to_pay())

        marker('Setting discount')
        self.discount_slave = SaleDiscountSlave(self.store, self.model,
                                                self.model_type)

        if sysparam.get_bool('USE_TRADE_AS_DISCOUNT'):
            self.subtotal_expander.set_expanded(True)
            self.discount_slave.discount_value_ck.set_active(True)
            self.discount_slave.update_sale_discount()
        marker('Finshed setting up discount')

        self.discount_slave.connect('discount-changed',
                                    self.on_discount_slave_changed)
        slave_holder = 'discount_surcharge_slave'
        if self.get_slave(slave_holder):
            self.detach_slave(slave_holder)
        self.attach_slave(slave_holder, self.discount_slave)
        marker('Finished setting up slaves')

    def setup_proxies(self):
        marker('Setting up proxies')
        self.setup_widgets()
        self.proxy = self.add_proxy(self.model, self.proxy_widgets)
        if self.model.client:
            self.client_gadget.set_editable(False)
        if sysparam.get_bool('ASK_SALES_CFOP'):
            self.add_proxy(self.model, SalesPersonStep.cfop_widgets)
        marker('Finished setting up proxies')

    #
    # Callbacks
    #

    def on_client__content_changed(self, entry):
        # This gets called before setup_slaves, but we must wait until slaves
        # are setup correctly
        if not self.pm_slave:
            return
        self.discount_slave.update_max_discount()
        self.pm_slave.set_client(client=self.model.client,
                                 total_amount=self.wizard.get_total_to_pay())

    def on_payment_method_changed(self, slave, method):
        self.force_validation()
        self._update_next_step(method)

    def on_client__validate(self, widget, client):
        if not client:
            return

        # this is used to avoid some tests from crashing
        if self.pm_slave is None:
            return

        method = self.pm_slave.get_selected_method()
        try:
            client.can_purchase(method, self.get_remaining_value())
        except SellError as e:
            return ValidationError(e)

        return StockOperationPersonValidationEvent.emit(
            client.person, type(client))

    def on_create_transporter__clicked(self, button):
        store = api.new_store()
        transporter = store.fetch(self.model.transporter)
        model = run_person_role_dialog(TransporterEditor, self.wizard, store,
                                       transporter)
        rv = store.confirm(model)
        store.close()
        if rv:
            self._fill_transporter_combo()
            model = self.store.fetch(model)
            self.transporter.select(model)

    def on_discount_slave_changed(self, slave):
        self._update_totals()
        self.client.validate()

    def on_observations_button__clicked(self, *args):
        self.store.savepoint('before_run_notes_editor')

        model = self.model.comments.first()
        if not model:
            model = SaleComment(store=self.store,
                                sale=self.model,
                                author=api.get_current_user(self.store))
        rv = run_dialog(NoteEditor,
                        self.wizard,
                        self.store,
                        model,
                        'comment',
                        title=_('Sale observations'))
        if not rv:
            self.store.rollback_to_savepoint('before_run_notes_editor')

    def on_create_cfop__clicked(self, widget):
        self.store.savepoint('before_run_editor_cfop')
        cfop = run_dialog(CfopEditor, self.wizard, self.store, None)
        if cfop:
            self.cfop.append_item(cfop.get_description(), cfop)
            self.cfop.select_item_by_data(cfop)
        else:
            self.store.rollback_to_savepoint('before_run_editor_cfop')

    def on_invoice_number__validate(self, widget, value):
        if not 0 < value <= 999999999:
            return ValidationError(
                _("Invoice number must be between 1 and 999999999"))

        invoice = self.model.invoice
        branch = self.model.branch
        if invoice.check_unique_invoice_number_by_branch(value, branch):
            return ValidationError(_(u'Invoice number already used.'))

    def on_transporter__validate(self, widget, transporter):
        return StockOperationPersonValidationEvent.emit(
            transporter.person, type(transporter))
示例#7
0
 def _setup_client_widget(self):
     self.client_gadget = ClientEntryGadget(entry=self.client,
                                            store=self.store,
                                            initial_value=self.model.client,
                                            parent=self)
示例#8
0
class StartSaleQuoteStep(WizardEditorStep):
    gladefile = 'SalesPersonQuoteWizardStep'
    model_type = Sale
    sale_widgets = ['client', 'salesperson', 'expire_date', 'client_category']
    invoice_widgets = ['operation_nature']
    cfop_widgets = ['cfop']
    proxy_widgets = sale_widgets + invoice_widgets + cfop_widgets

    def _setup_widgets(self):
        # Salesperson combo
        salespersons = SalesPerson.get_active_salespersons(self.store)
        self.salesperson.prefill(salespersons)

        change_salesperson = sysparam.get_int('ACCEPT_CHANGE_SALESPERSON')
        if change_salesperson == ChangeSalespersonPolicy.ALLOW:
            self.salesperson.grab_focus()
        elif change_salesperson == ChangeSalespersonPolicy.DISALLOW:
            self.salesperson.set_sensitive(False)
        elif change_salesperson == ChangeSalespersonPolicy.FORCE_CHOOSE:
            self.model.salesperson = None
            self.salesperson.grab_focus()
        else:
            raise AssertionError

        # CFOP combo
        if sysparam.get_bool('ASK_SALES_CFOP'):
            cfops = CfopData.get_for_sale(self.store)
            self.cfop.prefill(api.for_combo(cfops))
        else:
            self.cfop_lbl.hide()
            self.cfop.hide()
            self.create_cfop.hide()

        self._fill_clients_category_combo()
        self._setup_clients_widget()

        self._client_credit_set_visible(bool(self.client.read()))

    def _client_credit_set_visible(self, visible):
        if visible and self.model.client:
            self.client_credit.set_text(
                self.model.client.credit_account_balance.format(precision=2))
        self.client_credit.set_visible(visible)
        self.client_credit_lbl.set_visible(visible)

    def _setup_clients_widget(self):
        self.client_gadget = ClientEntryGadget(entry=self.client,
                                               store=self.store,
                                               initial_value=self.model.client,
                                               parent=self.wizard)

    def _fill_clients_category_combo(self):
        categories = self.store.find(ClientCategory).order_by(
            ClientCategory.name)
        self.client_category.prefill(api.for_combo(categories, empty=''))

        if categories.is_empty():
            self.client_category.hide()
            self.client_category_lbl.hide()

    def post_init(self):
        self.toogle_client_details()
        self.register_validate_function(self.wizard.refresh_next)
        self.force_validation()

    def next_step(self):
        return SaleQuoteItemStep(self.wizard, self, self.store, self.model)

    def has_previous_step(self):
        return False

    def setup_proxies(self):
        self._setup_widgets()
        self.sale_proxy = self.add_proxy(self.model,
                                         StartSaleQuoteStep.sale_widgets)
        self.invoice_proxy = self.add_proxy(self.model.invoice,
                                            StartSaleQuoteStep.invoice_widgets)
        if sysparam.get_bool('ASK_SALES_CFOP'):
            self.add_proxy(self.model, StartSaleQuoteStep.cfop_widgets)

        expire_delta = sysparam.get_int('EXPIRATION_SALE_QUOTE_DATE')
        if expire_delta > 0 and not self.model.expire_date:
            # Only set the expire date if id doesn't already have one.
            self.expire_date.update(localtoday() +
                                    relativedelta(days=expire_delta))

    def toogle_client_details(self):
        client = self.model.client

        if client and client.status != Client.STATUS_SOLVENT:
            self.client_gadget.update_edit_button(
                gtk.STOCK_DIALOG_WARNING, _("The client is not solvent"))

    #
    #   Callbacks
    #

    def after_client__content_changed(self, widget):
        # During the setup of client_gadget, the client is changed. So this method is
        # called before the client_gadget be completely created.
        # Check if the client_gadget was created to continue setting other widgets.
        if not hasattr(self, 'client_gadget'):
            return

        client = self.model.client
        self.toogle_client_details()
        self._client_credit_set_visible(bool(client))
        if not client:
            return
        self.client_category.select(client.category)

    def on_expire_date__validate(self, widget, value):
        # open_date has a seconds precision, so that why we are rounding it to
        # date here.
        if value.date() < self.model.open_date.date():
            msg = _(u"The expire date must be after the sale open date")
            return ValidationError(msg)

    def on_notes_button__clicked(self, *args):
        self.store.savepoint('before_run_notes_editor')

        model = self.model.comments.first()
        if not model:
            model = SaleComment(store=self.store,
                                sale=self.model,
                                author=api.get_current_user(self.store))
        rv = run_dialog(NoteEditor,
                        self.wizard,
                        self.store,
                        model,
                        'comment',
                        title=_('Additional Information'))
        if not rv:
            self.store.rollback_to_savepoint('before_run_notes_editor')

    def on_create_cfop__clicked(self, widget):
        self.store.savepoint('before_run_editor_cfop')
        cfop = run_dialog(CfopEditor, self.wizard, self.store, None)
        if cfop:
            self.cfop.append_item(cfop.get_description(), cfop)
            self.cfop.select_item_by_data(cfop)
        else:
            self.store.rollback_to_savepoint('before_run_editor_cfop')

    def on_client__validate(self, widget, client):
        return StockOperationPersonValidationEvent.emit(
            client.person, type(client))
示例#9
0
class SalesPersonStep(BaseMethodSelectionStep, WizardEditorStep):
    """ An abstract step which allows to define a salesperson, the sale's
    discount and surcharge, when it is needed.
    """
    gladefile = 'SalesPersonStep'
    model_type = Sale
    proxy_widgets = ['salesperson',
                     'client',
                     'transporter',
                     'cost_center']
    cfop_widgets = ('cfop', )

    def __init__(self, wizard, store, model, payment_group, previous=None):
        self.pm_slave = None
        self.payment_group = payment_group

        BaseMethodSelectionStep.__init__(self)
        marker("WizardEditorStep.__init__")
        WizardEditorStep.__init__(self, store, wizard, model,
                                  previous=previous)

        self._update_totals()
        self.update_discount_and_surcharge()

    #
    # Private API
    #

    def _update_totals(self):
        subtotal = self.wizard.get_subtotal()
        self.subtotal_lbl.update(subtotal)

        total_paid = self.wizard.get_total_paid()
        self.total_paid_lbl.update(total_paid)

        to_pay = self.model.get_total_sale_amount(subtotal=subtotal) - total_paid
        self.cash_change_slave.update_total_sale_amount(to_pay)
        self.total_lbl.update(to_pay)

    def _setup_clients_widget(self):
        marker('Filling clients')
        self.client_gadget = ClientEntryGadget(
            entry=self.client,
            store=self.store,
            initial_value=self.model.client,
            parent=self.wizard,
            run_editor=self._run_client_editor)
        marker('Filled clients')

    def _run_client_editor(self, store, model, description=None,
                           visual_mode=False):
        return run_person_role_dialog(ClientEditor, self.wizard, store, model,
                                      document=self.wizard._current_document,
                                      description=description,
                                      visual_mode=visual_mode)

    def _fill_transporter_combo(self):
        marker('Filling transporters')
        transporters = Transporter.get_active_transporters(self.store)
        items = api.for_person_combo(transporters)
        self.transporter.prefill(items)
        self.transporter.set_sensitive(len(items))
        marker('Filled transporters')

    def _fill_cost_center_combo(self):
        marker('Filling cost centers')
        cost_centers = CostCenter.get_active(self.store)

        # we keep this value because each call to is_empty() is a new sql query
        # to the database
        cost_centers_exists = not cost_centers.is_empty()

        if cost_centers_exists:
            self.cost_center.prefill(api.for_combo(cost_centers, attr='name',
                                                   empty=_('No cost center.')))
        self.cost_center.set_visible(cost_centers_exists)
        self.cost_center_lbl.set_visible(cost_centers_exists)
        marker('Filled cost centers')

    def _fill_cfop_combo(self):
        marker('Filling CFOPs')
        cfops = CfopData.get_for_sale(self.store)
        self.cfop.prefill(api.for_combo(cfops))
        marker('Filled CFOPs')

    #
    # Public API
    #

    def update_discount_and_surcharge(self):
        marker("update_discount_and_surcharge")
        # Here we need avoid to reset sale data defined when creating the
        # Sale in the POS application, i.e, we should not reset the
        # discount and surcharge if they are already set (this is the
        # case when one of the parameters, CONFIRM_SALES_ON_TILL or
        # USE_TRADE_AS_DISCOUNT is enabled).
        if (not sysparam.get_bool('CONFIRM_SALES_ON_TILL') and
                not sysparam.get_bool('USE_TRADE_AS_DISCOUNT')):
            self.model.discount_value = currency(0)
            self.model.surcharge_value = currency(0)

    def setup_widgets(self):
        marker('Setting up widgets')
        # Only quotes have expire date.
        self.expire_date.hide()
        self.expire_label.hide()

        # Hide client category widgets
        self.client_category_lbl.hide()
        self.client_category.hide()

        # if the NF-e plugin is active, the client is mandantory in this
        # wizard (in this situation, we have only quote sales).
        if self.model.status == Sale.STATUS_QUOTE:
            manager = get_plugin_manager()
            mandatory_client = manager.is_active('nfe')
            self.client.set_property('mandatory', mandatory_client)

        marker('Filling sales persons')
        salespersons = SalesPerson.get_active_salespersons(self.store)
        self.salesperson.prefill(salespersons)
        marker('Finished filling sales persons')

        marker('Read parameter')
        change_salesperson = sysparam.get_int('ACCEPT_CHANGE_SALESPERSON')
        if change_salesperson == ChangeSalespersonPolicy.ALLOW:
            self.salesperson.grab_focus()
        elif change_salesperson == ChangeSalespersonPolicy.DISALLOW:
            self.salesperson.set_sensitive(False)
        elif change_salesperson == ChangeSalespersonPolicy.FORCE_CHOOSE:
            self.model.salesperson = None
            self.salesperson.grab_focus()
        else:
            raise AssertionError
        marker('Finished reading parameter')
        self._setup_clients_widget()
        self._fill_transporter_combo()
        self._fill_cost_center_combo()

        if sysparam.get_bool('ASK_SALES_CFOP'):
            self._fill_cfop_combo()
        else:
            self.cfop_lbl.hide()
            self.cfop.hide()
            self.create_cfop.hide()

        marker('Finished setting up widgets')

    def _refresh_next(self, validation_value):
        self.client.validate(force=True)
        client_valid = self.client.is_valid()
        self.wizard.refresh_next(validation_value and client_valid)

    #
    # WizardStep hooks
    #

    def post_init(self):
        BaseMethodSelectionStep.post_init(self)

        marker('Entering post_init')
        if self.wizard.need_create_payment():
            self.wizard.payment_group.clear_unused()
        self.register_validate_function(self._refresh_next)
        self._update_next_step(self.get_selected_method())
        # If there's no salesperson, keep the focus there as it should be
        # selected first to have a nice flow
        if (hasattr(self, 'cash_change_slave') and
                self.model.salesperson is not None):
            self.cash_change_slave.received_value.grab_focus()

        self.force_validation()
        marker('Leaving post_init')

    def setup_slaves(self):
        marker('Setting up slaves')
        BaseMethodSelectionStep.setup_slaves(self)
        marker('Finished parent')

        self.pm_slave.set_client(self.model.client,
                                 total_amount=self.wizard.get_total_to_pay())

        marker('Setting discount')
        self.discount_slave = SaleDiscountSlave(self.store, self.model,
                                                self.model_type)

        if sysparam.get_bool('USE_TRADE_AS_DISCOUNT'):
            self.subtotal_expander.set_expanded(True)
            self.discount_slave.discount_value_ck.set_active(True)
            self.discount_slave.update_sale_discount()
        marker('Finshed setting up discount')

        self.discount_slave.connect('discount-changed',
                                    self.on_discount_slave_changed)
        slave_holder = 'discount_surcharge_slave'
        if self.get_slave(slave_holder):
            self.detach_slave(slave_holder)
        self.attach_slave(slave_holder, self.discount_slave)
        marker('Finished setting up slaves')

    def setup_proxies(self):
        marker('Setting up proxies')
        self.setup_widgets()
        self.proxy = self.add_proxy(self.model, self.proxy_widgets)
        if self.model.client:
            self.client_gadget.set_editable(False)
        if sysparam.get_bool('ASK_SALES_CFOP'):
            self.add_proxy(self.model, SalesPersonStep.cfop_widgets)
        marker('Finished setting up proxies')

    #
    # Callbacks
    #

    def on_client__content_changed(self, entry):
        # This gets called before setup_slaves, but we must wait until slaves
        # are setup correctly
        if not self.pm_slave:
            return
        self.discount_slave.update_max_discount()
        self.pm_slave.set_client(
            client=self.model.client,
            total_amount=self.wizard.get_total_to_pay())

    def on_payment_method_changed(self, slave, method):
        self.force_validation()
        self._update_next_step(method)

    def on_client__validate(self, widget, client):
        if not client:
            return

        # this is used to avoid some tests from crashing
        if self.pm_slave is None:
            return

        method = self.pm_slave.get_selected_method()
        try:
            client.can_purchase(method, self.get_remaining_value())
        except SellError as e:
            return ValidationError(e)

        return StockOperationPersonValidationEvent.emit(client.person, type(client))

    def on_create_transporter__clicked(self, button):
        store = api.new_store()
        transporter = store.fetch(self.model.transporter)
        model = run_person_role_dialog(TransporterEditor, self.wizard, store,
                                       transporter)
        rv = store.confirm(model)
        store.close()
        if rv:
            self._fill_transporter_combo()
            model = self.store.fetch(model)
            self.transporter.select(model)

    def on_discount_slave_changed(self, slave):
        self._update_totals()
        self.client.validate()

    def on_observations_button__clicked(self, *args):
        self.store.savepoint('before_run_notes_editor')

        model = self.model.comments.first()
        if not model:
            model = SaleComment(store=self.store, sale=self.model,
                                author=api.get_current_user(self.store))
        rv = run_dialog(NoteEditor, self.wizard, self.store, model, 'comment',
                        title=_('Sale observations'))
        if not rv:
            self.store.rollback_to_savepoint('before_run_notes_editor')

    def on_create_cfop__clicked(self, widget):
        self.store.savepoint('before_run_editor_cfop')
        cfop = run_dialog(CfopEditor, self.wizard, self.store, None)
        if cfop:
            self.cfop.append_item(cfop.get_description(), cfop)
            self.cfop.select_item_by_data(cfop)
        else:
            self.store.rollback_to_savepoint('before_run_editor_cfop')

    def on_invoice_number__validate(self, widget, value):
        if not 0 < value <= 999999999:
            return ValidationError(
                _("Invoice number must be between 1 and 999999999"))

        invoice = self.model.invoice
        branch = self.model.branch
        if invoice.check_unique_invoice_number_by_branch(value, branch):
            return ValidationError(_(u'Invoice number already used.'))

    def on_transporter__validate(self, widget, transporter):
        return StockOperationPersonValidationEvent.emit(transporter.person,
                                                        type(transporter))