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