Beispiel #1
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')

    invoice_widgets = ('invoice_number', )
    cfop_widgets = ('cfop', )

    def __init__(self, wizard, store, model, payment_group,
                 invoice_model):
        self.invoice_model = invoice_model

        self.payment_group = payment_group

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

        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 _update_widgets(self):
        has_client = bool(self.client.get_selected())
        self.pm_slave.method_set_sensitive(u'store_credit', has_client)
        self.pm_slave.method_set_sensitive(u'bill', has_client)

    def _fill_clients_combo(self):
        marker('Filling clients')
        # FIXME: This should not be using a normal ProxyComboEntry,
        #        we need a specialized widget that does the searching
        #        on demand.

        clients = Client.get_active_clients(self.store)
        self.client.prefill(api.for_person_combo(clients))
        self.client.set_sensitive(len(self.client.get_model()))
        marker('Filled clients')

    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 = self.store.find(CfopData)
        self.cfop.prefill(api.for_combo(cfops))
        marker('Filled CFOPs')

    def _create_client(self):
        store = api.new_store()
        client = run_person_role_dialog(ClientEditor, self.wizard, store, None)
        store.confirm(client)
        client = self.store.fetch(client)
        store.close()
        if not client:
            return
        if len(self.client) == 0:
            self._fill_clients_combo()
            return
        clients = self.client.get_model_items().values()
        if client in clients:
            if client.is_active:
                self.client.select(client)
            else:
                # remove client from combo
                self.client.select_item_by_data(client)
                iter = self.client.get_active_iter()
                model = self.client.get_model()
                model.remove(iter)
                # just in case the inactive client was selected before.
                self.client.select_item_by_position(0)
        elif client.is_active:
            self.client.append_item(client.person.name, client)
            self.client.select(client)
        self._update_widgets()

    #
    # 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 CONFIRM_SALES_ON_TILL parameter is enabled).
        if not sysparam(self.store).CONFIRM_SALES_ON_TILL:
            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 operation nature widgets
        self.operation_nature.hide()
        self.nature_lbl.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 = self.store.find(SalesPerson)
        self.salesperson.prefill(api.for_person_combo(salespersons))
        marker('Finished filling sales persons')

        marker('Read parameter')
        if not sysparam(self.store).ACCEPT_CHANGE_SALESPERSON:
            self.salesperson.set_sensitive(False)
        else:
            self.salesperson.grab_focus()
        marker('Finished reading parameter')
        self._fill_clients_combo()
        self._fill_transporter_combo()
        self._fill_cost_center_combo()

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

        # the maximum number allowed for an invoice is 999999999.
        self.invoice_number.set_adjustment(
            gtk.Adjustment(lower=1, upper=999999999, step_incr=1))

        if not self.model.invoice_number:
            new_invoice_number = Sale.get_last_invoice_number(self.store) + 1
            self.invoice_model.invoice_number = new_invoice_number
        else:
            new_invoice_number = self.model.invoice_number
            self.invoice_model.invoice_number = new_invoice_number
            self.invoice_number.set_sensitive(False)

        self.invoice_model.original_invoice = new_invoice_number
        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')
        self.toogle_client_details()
        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 hasattr(self, 'cash_change_slave'):
            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.method_set_sensitive(u'store_credit',
                                           bool(self.model.client))
        self.pm_slave.method_set_sensitive(u'bill',
                                           bool(self.model.client))

        marker('Setting discount')
        self.discount_slave = SaleDiscountSlave(self.store, self.model,
                                                self.model_type)
        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,
                                    SalesPersonStep.proxy_widgets)
        self.invoice_proxy = self.add_proxy(self.invoice_model,
                                            self.invoice_widgets)
        if self.model.client:
            self.client.set_sensitive(False)
            self.create_client.set_sensitive(False)
        if sysparam(self.store).ASK_SALES_CFOP:
            self.add_proxy(self.model, SalesPersonStep.cfop_widgets)
        marker('Finished setting up proxies')

    def toogle_client_details(self):
        client = self.client.read()
        self.client_details.set_sensitive(bool(client))

    #
    # Callbacks
    #

    def on_client__changed(self, entry):
        self.toogle_client_details()
        self._update_widgets()
        self.discount_slave.update_max_discount()

    def on_payment_method_changed(self, slave, method_name):
        self.client.validate(force=True)
        self._update_next_step(method_name)

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

        # this is used to avoid some tests from crashing
        if not hasattr(self, 'pm_slave'):
            return

        method = self.pm_slave.get_selected_method()
        try:
            client.can_purchase(method, self.model.get_total_sale_amount())
            self.wizard.refresh_next(True)
        except SellError as e:
            self.wizard.refresh_next(False)
            return ValidationError(e)

    def on_create_client__clicked(self, button):
        self._create_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_notes_button__clicked(self, *args):
        run_dialog(NoteEditor, self.wizard, self.store, self.model, 'notes',
                   title=_("Additional Information"))

    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"))

        exists = self.store.find(
            Sale, And(Sale.invoice_number == value,
                      Sale.id != self.model.id))
        if not exists.is_empty():
            return ValidationError(_(u'Invoice number already used.'))

    def on_client_details__clicked(self, button):
        client = self.model.client
        run_dialog(ClientDetailsDialog, self.wizard, self.store, client)
Beispiel #2
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))
Beispiel #3
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')

    invoice_widgets = ('invoice_number', )
    cfop_widgets = ('cfop', )

    def __init__(self, wizard, store, model, payment_group,
                 invoice_model, previous=None):
        self.invoice_model = invoice_model
        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 _get_client(self):
        client_id = self.client.read()
        if not client_id:
            return None
        return self.store.get(Client, client_id)

    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 = ClientSearchEntryGadget(
            entry=self.client,
            store=self.store,
            model=self.model,
            parent=self.wizard)
        marker('Filled clients')

    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')

    def _create_client(self):
        store = api.new_store()
        client = run_person_role_dialog(ClientEditor, self.wizard, store, None)
        store.confirm(client)

        if not client:
            return
        if len(self.client) == 0:
            self._setup_clients_widget()
            return
        clients = self.client.get_model_items().values()
        if client.id in clients:
            if client.is_active:
                self.client.select(client.id)
            else:
                # remove client from combo
                self.client.select_item_by_data(client.id)
                iter = self.client.get_active_iter()
                model = self.client.get_model()
                model.remove(iter)
                # just in case the inactive client was selected before.
                self.client.select_item_by_position(0)
        elif client.is_active:
            self.client.append_item(client.person.name, client.id)
            self.client.select(client.id)
        store.close()

    #
    # 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 = self.store.find(SalesPerson)
        self.salesperson.prefill(api.for_person_combo(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()

        # the maximum number allowed for an invoice is 999999999.
        self.invoice_number.set_adjustment(
            gtk.Adjustment(lower=1, upper=999999999, step_incr=1))

        if not self.model.invoice_number:
            new_invoice_number = Invoice.get_next_invoice_number(self.store)
            self.invoice_model.invoice_number = new_invoice_number
        else:
            new_invoice_number = self.model.invoice_number
            self.invoice_model.invoice_number = new_invoice_number
            self.invoice_number.set_sensitive(False)

        self.invoice_model.original_invoice = new_invoice_number
        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,
                                    SalesPersonStep.proxy_widgets)
        self.invoice_proxy = self.add_proxy(self.invoice_model,
                                            self.invoice_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._get_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_id):
        if not client_id:
            return

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

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

    def on_create_client__clicked(self, button):
        self._create_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.'))
Beispiel #4
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')

    invoice_widgets = ('invoice_number', )
    cfop_widgets = ('cfop', )

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

        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 _update_widgets(self):
        has_client = bool(self.client.get_selected())
        self.pm_slave.method_set_sensitive(u'store_credit', has_client)
        self.pm_slave.method_set_sensitive(u'bill', has_client)
        self.pm_slave.method_set_sensitive(u'credit', has_client)

    def _fill_clients_combo(self):
        marker('Filling clients')
        # FIXME: This should not be using a normal ProxyComboEntry,
        #        we need a specialized widget that does the searching
        #        on demand.

        clients = Client.get_active_clients(self.store)
        self.client.prefill(api.for_person_combo(clients))
        self.client.set_sensitive(len(self.client.get_model()))
        marker('Filled clients')

    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 = self.store.find(CfopData)
        self.cfop.prefill(api.for_combo(cfops))
        marker('Filled CFOPs')

    def _create_client(self):
        store = api.new_store()
        client = run_person_role_dialog(ClientEditor, self.wizard, store, None)
        store.confirm(client)
        client = self.store.fetch(client)
        store.close()
        if not client:
            return
        if len(self.client) == 0:
            self._fill_clients_combo()
            return
        clients = self.client.get_model_items().values()
        if client in clients:
            if client.is_active:
                self.client.select(client)
            else:
                # remove client from combo
                self.client.select_item_by_data(client)
                iter = self.client.get_active_iter()
                model = self.client.get_model()
                model.remove(iter)
                # just in case the inactive client was selected before.
                self.client.select_item_by_position(0)
        elif client.is_active:
            self.client.append_item(client.person.name, client)
            self.client.select(client)
        self._update_widgets()

    #
    # 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 CONFIRM_SALES_ON_TILL parameter is enabled).
        if not sysparam(self.store).CONFIRM_SALES_ON_TILL:
            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 operation nature widgets
        self.operation_nature.hide()
        self.nature_lbl.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 = self.store.find(SalesPerson)
        self.salesperson.prefill(api.for_person_combo(salespersons))
        marker('Finished filling sales persons')

        marker('Read parameter')
        if not sysparam(self.store).ACCEPT_CHANGE_SALESPERSON:
            self.salesperson.set_sensitive(False)
        else:
            self.salesperson.grab_focus()
        marker('Finished reading parameter')
        self._fill_clients_combo()
        self._fill_transporter_combo()
        self._fill_cost_center_combo()

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

        # the maximum number allowed for an invoice is 999999999.
        self.invoice_number.set_adjustment(
            gtk.Adjustment(lower=1, upper=999999999, step_incr=1))

        if not self.model.invoice_number:
            new_invoice_number = Sale.get_last_invoice_number(self.store) + 1
            self.invoice_model.invoice_number = new_invoice_number
        else:
            new_invoice_number = self.model.invoice_number
            self.invoice_model.invoice_number = new_invoice_number
            self.invoice_number.set_sensitive(False)

        self.invoice_model.original_invoice = new_invoice_number
        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')
        self.toogle_client_details()
        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 hasattr(self, 'cash_change_slave'):
            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.method_set_sensitive(u'store_credit',
                                           bool(self.model.client))
        self.pm_slave.method_set_sensitive(u'bill', bool(self.model.client))
        self.pm_slave.method_set_sensitive(u'credit', bool(self.model.client))

        marker('Setting discount')
        self.discount_slave = SaleDiscountSlave(self.store, self.model,
                                                self.model_type)
        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, SalesPersonStep.proxy_widgets)
        self.invoice_proxy = self.add_proxy(self.invoice_model,
                                            self.invoice_widgets)
        if self.model.client:
            self.client.set_sensitive(False)
            self.create_client.set_sensitive(False)
        if sysparam(self.store).ASK_SALES_CFOP:
            self.add_proxy(self.model, SalesPersonStep.cfop_widgets)
        marker('Finished setting up proxies')

    def toogle_client_details(self):
        client = self.client.read()
        self.client_details.set_sensitive(bool(client))

    #
    # Callbacks
    #

    def on_client__changed(self, entry):
        self.toogle_client_details()
        self._update_widgets()
        self.discount_slave.update_max_discount()

    def on_payment_method_changed(self, slave, method_name):
        self.client.validate(force=True)
        self._update_next_step(method_name)

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

        # this is used to avoid some tests from crashing
        if not hasattr(self, 'pm_slave'):
            return

        method = self.pm_slave.get_selected_method()
        try:
            client.can_purchase(method, self.model.get_total_sale_amount())
            self.wizard.refresh_next(True)
        except SellError as e:
            self.wizard.refresh_next(False)
            return ValidationError(e)

    def on_create_client__clicked(self, button):
        self._create_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):
        run_dialog(NoteEditor,
                   self.wizard,
                   self.store,
                   self.model,
                   'notes',
                   title=_("Additional Information"))

    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"))

        exists = self.store.find(
            Sale, And(Sale.invoice_number == value, Sale.id != self.model.id))
        if not exists.is_empty():
            return ValidationError(_(u'Invoice number already used.'))

    def on_client_details__clicked(self, button):
        client = self.model.client
        run_dialog(ClientDetailsDialog, self.wizard, self.store, client)