class SellableEditor(BaseEditor): """This is a base class for ProductEditor and ServiceEditor and should be used when editing sellable objects. Note that sellable objects are instances inherited by Sellable.""" # This must be be properly defined in the child classes model_name = None model_type = None gladefile = "SellableEditor" confirm_widgets = ["description", "cost", "price"] ui_form_name = None sellable_tax_widgets = ("tax_constant", "tax_value") sellable_widgets = ( "code", "barcode", "description", "category_combo", "cost", "price", "statuses_combo", "default_sale_cfop", "unit_combo", ) proxy_widgets = sellable_tax_widgets + sellable_widgets def __init__(self, store, model=None, visual_mode=False): from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave is_new = not model self._sellable = None self._demo_mode = sysparam(store).DEMO_MODE self._requires_weighing_text = "<b>%s</b>" % api.escape(_("This unit type requires weighing")) if self.ui_form_name: self.db_form = DatabaseForm(store, self.ui_form_name) else: self.db_form = None BaseEditor.__init__(self, store, model, visual_mode) self.enable_window_controls() if self._demo_mode: self._add_demo_warning() # code suggestion edit_code_product = sysparam(self.store).EDIT_CODE_PRODUCT self.code.set_sensitive(not edit_code_product and not self.visual_mode) if not self.code.read(): code = u"%d" % self._sellable.id self.code.update(code) self.description.grab_focus() self.table.set_focus_chain( [ self.code, self.barcode, self.default_sale_cfop, self.description, self.cost_hbox, self.price_hbox, self.consignment_box, self.statuses_combo, self.category_combo, self.tax_hbox, self.unit_combo, ] ) print_labels_button = self.add_button("print_labels", gtk.STOCK_PRINT) print_labels_button.connect("clicked", self.on_print_labels_clicked, "print_labels") label = print_labels_button.get_children()[0] label = label.get_children()[0].get_children()[1] label.set_label(_(u"Print labels")) self.setup_widgets() if not is_new and not self.visual_mode: # Although a sellable can be both removed/closed, we show only one, # to avoid having *lots* of buttons. If it can be removed and # closed, probably the user will prefer to remove if self._sellable.can_remove(): self._add_delete_button() elif self._sellable.is_closed(): self._add_reopen_button() elif self._sellable.can_close(): self._add_close_button() self.set_main_tab_label(self.model_name) price_slave = CategoryPriceSlave(self.store, self.model.sellable, self.visual_mode) self.add_extra_tab(_(u"Category Prices"), price_slave) self._setup_ui_forms() def _add_demo_warning(self): self.set_message( _( "This is a demostration mode of Stoq, you cannot create more than %d products.\n" "To avoid this limitation, enable production mode." ) % (_DEMO_PRODUCT_LIMIT) ) if self.store.find(Sellable).count() > _DEMO_PRODUCT_LIMIT: self.disable_ok() def _add_extra_button(self, label, stock=None, callback_func=None, connect_on="clicked"): button = self.add_button(label, stock) if callback_func: button.connect(connect_on, callback_func, label) def _add_delete_button(self): self._add_extra_button(_("Remove"), gtk.STOCK_DELETE, self._on_delete_button__clicked) def _add_close_button(self): if self._sellable.product: label = _("Close Product") else: label = _("Close Service") self._add_extra_button(label, None, self._on_close_sellable_button__clicked) def _add_reopen_button(self): if self._sellable.product: label = _("Reopen Product") else: label = _("Reopen Service") self._add_extra_button(label, None, self._on_reopen_sellable_button__clicked) def _setup_ui_forms(self): if not self.db_form: return self.db_form.update_widget(self.code, other=self.code_lbl) self.db_form.update_widget(self.barcode, other=self.barcode_lbl) self.db_form.update_widget(self.category_combo, other=self.category_lbl) # # Public API # def set_main_tab_label(self, tabname): self.sellable_notebook.set_tab_label(self.sellable_tab, gtk.Label(tabname)) def add_extra_tab(self, tabname, tabslave): self.sellable_notebook.set_show_tabs(True) self.sellable_notebook.set_show_border(True) event_box = gtk.EventBox() event_box.show() self.sellable_notebook.append_page(event_box, gtk.Label(tabname)) self.attach_slave(tabname, tabslave, event_box) def set_widget_formats(self): for widget in (self.cost, self.price): widget.set_adjustment(gtk.Adjustment(lower=0, upper=sys.maxint, step_incr=1)) self.requires_weighing_label.set_size("small") self.requires_weighing_label.set_text("") self.status_unavailable_label.set_size("small") self.status_unavailable_label.set_text("") def edit_sale_price(self): sellable = self.model.sellable self.store.savepoint("before_run_editor_sellable_price") result = run_dialog(SellablePriceEditor, self, self.store, sellable) if result: self.sellable_proxy.update("price") else: self.store.rollback_to_savepoint("before_run_editor_sellable_price") def setup_widgets(self): raise NotImplementedError def update_requires_weighing_label(self): unit = self._sellable.unit if unit and unit.unit_index == UnitType.WEIGHT: self.requires_weighing_label.set_text(self._requires_weighing_text) else: self.requires_weighing_label.set_text("") def _update_tax_value(self): if not hasattr(self, "tax_proxy"): return self.tax_proxy.update("tax_constant.tax_value") def get_taxes(self): """Subclasses may override this method to provide a custom tax selection. :returns: a list of tuples containing the tax description and a :class:`stoqlib.domain.sellable.SellableTaxConstant` object. """ return [] def _fill_categories(self): categories = self.store.find(SellableCategory) self.category_combo.prefill(api.for_combo(categories, attr="full_description")) # # BaseEditor hooks # def update_visual_mode(self): self.add_category.set_sensitive(False) self.sale_price_button.set_sensitive(False) def setup_sellable_combos(self): self._fill_categories() self.edit_category.set_sensitive(False) self.statuses_combo.prefill([(v, k) for k, v in Sellable.statuses.items()]) self.statuses_combo.set_sensitive(False) cfops = self.store.find(CfopData) self.default_sale_cfop.prefill(api.for_combo(cfops, empty="")) self.setup_unit_combo() def setup_unit_combo(self): units = self.store.find(SellableUnit) self.unit_combo.prefill(api.for_combo(units, empty=_("No units"))) def setup_tax_constants(self): taxes = self.get_taxes() self.tax_constant.prefill(taxes) def setup_proxies(self): self.set_widget_formats() self._sellable = self.model.sellable self.add_category.set_tooltip_text(_("Add a new category")) self.edit_category.set_tooltip_text(_("Edit the selected category")) self.setup_sellable_combos() self.setup_tax_constants() self.tax_proxy = self.add_proxy(self._sellable, SellableEditor.sellable_tax_widgets) self.sellable_proxy = self.add_proxy(self._sellable, SellableEditor.sellable_widgets) self.update_requires_weighing_label() def _run_category_editor(self, category=None): self.store.savepoint("before_run_editor_sellable_category") model = run_dialog(SellableCategoryEditor, self, self.store, category) if model: self._fill_categories() self.category_combo.select(model) else: self.store.rollback_to_savepoint("before_run_editor_sellable_category") # # Kiwi handlers # def _on_delete_button__clicked(self, button, parent_button_label=None): sellable_description = self._sellable.get_description() msg = _("This will delete '%s' from the database. Are you sure?") % sellable_description if not yesno(msg, gtk.RESPONSE_NO, _("Delete"), _("Keep")): return try: self._sellable.remove() except IntegrityError, details: warning(_("It was not possible to remove '%s'") % sellable_description, str(details)) return self.confirm()
class _PersonEditorTemplate(BaseEditorSlave): model_type = Person gladefile = 'PersonEditorTemplate' proxy_widgets = ('name', 'phone_number', 'fax_number', 'mobile_number', 'email') def __init__(self, store, model, visual_mode, ui_form_name, parent): self._parent = parent if ui_form_name: self.db_form = DatabaseForm(store, ui_form_name) else: self.db_form = None super(_PersonEditorTemplate, self).__init__(store, model, visual_mode=visual_mode) self._check_new_person() def _check_new_person(self): self.is_new_person = False # If this person is not in the default store, then it was created # inside another transaction that was not commited yet. default_store = api.get_default_store() if default_store.find(Person, id=self.model.id).is_empty(): self.is_new_person = True # # BaseEditorSlave hooks # def create_model(self, store): return Person(name=u"", store=store) def setup_proxies(self): self._setup_widgets() self._setup_form_fields() self.proxy = self.add_proxy(self.model, _PersonEditorTemplate.proxy_widgets) def setup_slaves(self): self.address_slave = AddressSlave( self.store, self.model, self.model.get_main_address(), visual_mode=self.visual_mode, db_form=self.db_form) self.attach_slave('address_holder', self.address_slave) self.attach_model_slave('note_holder', NoteSlave, self.model) def on_confirm(self): main_address = self.address_slave.model main_address.person = self.model # # Public API # def add_extra_tab(self, tab_label, slave, position=None): """Adds an extra tab to the editor :param tab_label: the label that will be display on the tab :param slave: the slave that will be attached to the new tab :param position: the position the tab will be attached """ event_box = gtk.EventBox() self.person_notebook.append_page(event_box, gtk.Label(tab_label)) self.attach_slave(tab_label, slave, event_box) event_box.show() if position is not None: self.person_notebook.reorder_child(event_box, position) self.person_notebook.set_current_page(position) def set_phone_number(self, phone_number): self.model.phone_number = phone_number self.proxy.update('phone_number') def attach_role_slave(self, slave): self.attach_slave('role_holder', slave) def attach_model_slave(self, name, slave_type, slave_model): slave = slave_type(self.store, slave_model, visual_mode=self.visual_mode) self.attach_slave(name, slave) return slave # # Kiwi handlers # def on_name__map(self, entry): self.name.grab_focus() def on_address_button__clicked(self, button): main_address = self.model.get_main_address() if not main_address.is_valid_model(): msg = _(u"You must define a valid main address before\n" "adding additional addresses") warning(msg) return result = run_dialog(AddressAdditionDialog, self._parent, self.store, person=self.model, reuse_store=self.is_new_person) if not result: return new_main_address = self.model.get_main_address() if new_main_address is not main_address: self.address_slave.set_model(new_main_address) def on_contact_info_button__clicked(self, button): run_dialog(ContactInfoListDialog, self._parent, self.store, person=self.model, reuse_store=self.is_new_person) def on_calls_button__clicked(self, button): run_dialog(CallsSearch, self._parent, self.store, person=self.model, reuse_store=self.is_new_person) def on_credit_check_history_button__clicked(self, button): run_dialog(CreditCheckHistorySearch, self._parent, self.store, client=self.model.client, reuse_store=self.is_new_person) # # Private API # def _setup_widgets(self): individual = self.model.individual company = self.model.company if not (individual or company): raise DatabaseInconsistency('A person must have at least a ' 'company or an individual set.') tab_child = self.person_data_tab if individual and company: tab_text = _('Individual/Company Data') self.company_frame.set_label(_('Company Data')) self.company_frame.show() self.individual_frame.set_label(_('Individual Data')) self.individual_frame.show() elif individual: tab_text = _('Individual Data') self.company_frame.hide() self.individual_frame.set_label('') self.individual_frame.show() else: tab_text = _('Company Data') self.individual_frame.hide() self.company_frame.set_label('') self.company_frame.show() self.person_notebook.set_tab_label_text(tab_child, tab_text) addresses = self.model.get_total_addresses() if addresses == 2: self.address_button.set_label(_("1 More Address...")) elif addresses > 2: self.address_button.set_label(_("%i More Addresses...") % (addresses - 1)) if not self.model.client: self.credit_check_history_button.hide() def _setup_form_fields(self): if not self.db_form: return self.db_form.update_widget(self.name, other=self.name_lbl) self.db_form.update_widget(self.phone_number, other=self.phone_number_lbl) self.db_form.update_widget(self.fax_number, u'fax', other=self.fax_lbl) self.db_form.update_widget(self.email, other=self.email_lbl) self.db_form.update_widget(self.mobile_number, other=self.mobile_lbl)
class _PersonEditorTemplate(BaseEditorSlave): model_type = Person gladefile = 'PersonEditorTemplate' proxy_widgets = ('name', 'phone_number', 'fax_number', 'mobile_number', 'email') def __init__(self, store, model, visual_mode, ui_form_name, parent): self._parent = parent if ui_form_name: self.db_form = DatabaseForm(store, ui_form_name) else: self.db_form = None super(_PersonEditorTemplate, self).__init__(store, model, visual_mode=visual_mode) self._check_new_person() def _check_new_person(self): self.is_new_person = False # If this person is not in the default store, then it was created # inside another transaction that was not commited yet. default_store = api.get_default_store() if default_store.find(Person, id=self.model.id).is_empty(): self.is_new_person = True # # BaseEditorSlave hooks # def create_model(self, store): return Person(name=u"", store=store) def setup_proxies(self): self._setup_widgets() self._setup_form_fields() self.proxy = self.add_proxy(self.model, _PersonEditorTemplate.proxy_widgets) def setup_slaves(self): self.address_slave = AddressSlave(self.store, self.model, self.model.get_main_address(), visual_mode=self.visual_mode, db_form=self.db_form) self.attach_slave('address_holder', self.address_slave) self.attach_model_slave('note_holder', NoteSlave, self.model) def on_confirm(self): main_address = self.address_slave.model main_address.person = self.model # # Public API # def add_extra_tab(self, tab_label, slave, position=None): """Adds an extra tab to the editor :param tab_label: the label that will be display on the tab :param slave: the slave that will be attached to the new tab :param position: the position the tab will be attached """ event_box = gtk.EventBox() self.person_notebook.append_page(event_box, gtk.Label(tab_label)) self.attach_slave(tab_label, slave, event_box) event_box.show() if position is not None: self.person_notebook.reorder_child(event_box, position) self.person_notebook.set_current_page(position) def set_phone_number(self, phone_number): self.model.phone_number = phone_number self.proxy.update('phone_number') def attach_role_slave(self, slave): self.attach_slave('role_holder', slave) def attach_model_slave(self, name, slave_type, slave_model): slave = slave_type(self.store, slave_model, visual_mode=self.visual_mode) self.attach_slave(name, slave) return slave # # Kiwi handlers # def on_name__map(self, entry): self.name.grab_focus() def on_address_button__clicked(self, button): main_address = self.model.get_main_address() if not main_address.is_valid_model(): msg = _(u"You must define a valid main address before\n" "adding additional addresses") warning(msg) return result = run_dialog(AddressAdditionDialog, self._parent, self.store, person=self.model, reuse_store=self.is_new_person) if not result: return new_main_address = self.model.get_main_address() if new_main_address is not main_address: self.address_slave.set_model(new_main_address) def on_contact_info_button__clicked(self, button): run_dialog(ContactInfoListDialog, self._parent, self.store, person=self.model, reuse_store=self.is_new_person) def on_calls_button__clicked(self, button): run_dialog(CallsSearch, self._parent, self.store, person=self.model, reuse_store=self.is_new_person) def on_credit_check_history_button__clicked(self, button): run_dialog(CreditCheckHistorySearch, self._parent, self.store, client=self.model.client, reuse_store=self.is_new_person) # # Private API # def _setup_widgets(self): individual = self.model.individual company = self.model.company if not (individual or company): raise DatabaseInconsistency('A person must have at least a ' 'company or an individual set.') tab_child = self.person_data_tab if individual and company: tab_text = _('Individual/Company Data') self.company_frame.set_label(_('Company Data')) self.company_frame.show() self.individual_frame.set_label(_('Individual Data')) self.individual_frame.show() elif individual: tab_text = _('Individual Data') self.company_frame.hide() self.individual_frame.set_label('') self.individual_frame.show() else: tab_text = _('Company Data') self.individual_frame.hide() self.company_frame.set_label('') self.company_frame.show() self.person_notebook.set_tab_label_text(tab_child, tab_text) addresses = self.model.get_total_addresses() if addresses == 2: self.address_button.set_label(_("1 More Address...")) elif addresses > 2: self.address_button.set_label( _("%i More Addresses...") % (addresses - 1)) if not self.model.client: self.credit_check_history_button.hide() def _setup_form_fields(self): if not self.db_form: return self.db_form.update_widget(self.name, other=self.name_lbl) self.db_form.update_widget(self.phone_number, other=self.phone_number_lbl) self.db_form.update_widget(self.fax_number, u'fax', other=self.fax_lbl) self.db_form.update_widget(self.email, other=self.email_lbl) self.db_form.update_widget(self.mobile_number, other=self.mobile_lbl)
class SellableEditor(BaseEditor): """This is a base class for ProductEditor and ServiceEditor and should be used when editing sellable objects. Note that sellable objects are instances inherited by Sellable.""" # This must be be properly defined in the child classes model_name = None model_type = None gladefile = 'SellableEditor' confirm_widgets = ['description', 'cost', 'price'] ui_form_name = None sellable_tax_widgets = ('tax_constant', 'tax_value', ) sellable_widgets = ('code', 'barcode', 'description', 'category_combo', 'cost', 'price', 'statuses_combo', 'default_sale_cfop', 'unit_combo') proxy_widgets = (sellable_tax_widgets + sellable_widgets) def __init__(self, store, model=None, visual_mode=False): from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave is_new = not model self._sellable = None self._demo_mode = sysparam(store).DEMO_MODE self._requires_weighing_text = ( "<b>%s</b>" % api.escape(_("This unit type requires weighing"))) if self.ui_form_name: self.db_form = DatabaseForm(store, self.ui_form_name) else: self.db_form = None BaseEditor.__init__(self, store, model, visual_mode) self.enable_window_controls() if self._demo_mode: self._add_demo_warning() # code suggestion edit_code_product = sysparam(self.store).EDIT_CODE_PRODUCT self.code.set_sensitive(not edit_code_product and not self.visual_mode) if not self.code.read(): self._update_default_sellable_code() self.description.grab_focus() self.table.set_focus_chain([self.code, self.barcode, self.default_sale_cfop, self.description, self.cost_hbox, self.price_hbox, self.consignment_box, self.manage_stock, self.statuses_combo, self.category_combo, self.tax_hbox, self.unit_combo, ]) print_labels_button = self.add_button('print_labels', gtk.STOCK_PRINT) print_labels_button.connect('clicked', self.on_print_labels_clicked, 'print_labels') label = print_labels_button.get_children()[0] label = label.get_children()[0].get_children()[1] label.set_label(_(u'Print labels')) self.setup_widgets() if not is_new and not self.visual_mode: # Although a sellable can be both removed/closed, we show only one, # to avoid having *lots* of buttons. If it's closed, provide a way # to reopen it, else, show a delete button if it can be removed # or a close button if it can be closed if self._sellable.is_closed(): self._add_reopen_button() elif self._sellable.can_remove(): self._add_delete_button() elif self._sellable.can_close(): self._add_close_button() self.set_main_tab_label(self.model_name) price_slave = CategoryPriceSlave(self.store, self.model.sellable, self.visual_mode) self.add_extra_tab(_(u'Category Prices'), price_slave) self._setup_ui_forms() def _add_demo_warning(self): self.set_message( _("This is a demostration mode of Stoq, you cannot create more than %d products.\n" "To avoid this limitation, enable production mode.") % ( _DEMO_PRODUCT_LIMIT)) if self.store.find(Sellable).count() > _DEMO_PRODUCT_LIMIT: self.disable_ok() def _add_extra_button(self, label, stock=None, callback_func=None, connect_on='clicked'): button = self.add_button(label, stock) if callback_func: button.connect(connect_on, callback_func, label) def _add_delete_button(self): self._add_extra_button(_('Remove'), gtk.STOCK_DELETE, self._on_delete_button__clicked) def _add_close_button(self): if self._sellable.product: label = _('Close Product') else: label = _('Close Service') self._add_extra_button(label, None, self._on_close_sellable_button__clicked) def _add_reopen_button(self): if self._sellable.product: label = _('Reopen Product') else: label = _('Reopen Service') self._add_extra_button(label, None, self._on_reopen_sellable_button__clicked) def _update_default_sellable_code(self): code = self.store.find(Sellable).max(Sellable.code) self.code.update(next_value_for(code)) def _setup_ui_forms(self): if not self.db_form: return self.db_form.update_widget(self.code, other=self.code_lbl) self.db_form.update_widget(self.barcode, other=self.barcode_lbl) self.db_form.update_widget(self.category_combo, other=self.category_lbl) # # Public API # def set_main_tab_label(self, tabname): self.sellable_notebook.set_tab_label(self.sellable_tab, gtk.Label(tabname)) def add_extra_tab(self, tabname, tabslave): self.sellable_notebook.set_show_tabs(True) self.sellable_notebook.set_show_border(True) event_box = gtk.EventBox() event_box.show() self.sellable_notebook.append_page(event_box, gtk.Label(tabname)) self.attach_slave(tabname, tabslave, event_box) def set_widget_formats(self): for widget in (self.cost, self.price): widget.set_adjustment(gtk.Adjustment(lower=0, upper=sys.maxint, step_incr=1)) self.requires_weighing_label.set_size("small") self.requires_weighing_label.set_text("") def edit_sale_price(self): sellable = self.model.sellable self.store.savepoint('before_run_editor_sellable_price') result = run_dialog(SellablePriceEditor, self, self.store, sellable) if result: self.sellable_proxy.update('price') else: self.store.rollback_to_savepoint('before_run_editor_sellable_price') def setup_widgets(self): raise NotImplementedError def update_requires_weighing_label(self): unit = self._sellable.unit if unit and unit.unit_index == UnitType.WEIGHT: self.requires_weighing_label.set_text(self._requires_weighing_text) else: self.requires_weighing_label.set_text("") def _update_tax_value(self): if not hasattr(self, 'tax_proxy'): return self.tax_proxy.update('tax_constant.tax_value') def get_taxes(self): """Subclasses may override this method to provide a custom tax selection. :returns: a list of tuples containing the tax description and a :class:`stoqlib.domain.sellable.SellableTaxConstant` object. """ return [] def _fill_categories(self): categories = self.store.find(SellableCategory) self.category_combo.prefill(api.for_combo(categories, attr='full_description')) # # BaseEditor hooks # def update_visual_mode(self): self.add_category.set_sensitive(False) self.sale_price_button.set_sensitive(False) def setup_sellable_combos(self): self._fill_categories() self.edit_category.set_sensitive(False) # FIXME: Make the combo a label self.statuses_combo.prefill( [(v, k) for k, v in Sellable.statuses.items()]) self.statuses_combo.set_sensitive(False) cfops = self.store.find(CfopData) self.default_sale_cfop.prefill(api.for_combo(cfops, empty='')) self.setup_unit_combo() def setup_unit_combo(self): units = self.store.find(SellableUnit) self.unit_combo.prefill(api.for_combo(units, empty=_('No units'))) def setup_tax_constants(self): taxes = self.get_taxes() self.tax_constant.prefill(taxes) def setup_proxies(self): self.set_widget_formats() self._sellable = self.model.sellable self.add_category.set_tooltip_text(_("Add a new category")) self.edit_category.set_tooltip_text(_("Edit the selected category")) self.setup_sellable_combos() self.setup_tax_constants() self.tax_proxy = self.add_proxy(self._sellable, SellableEditor.sellable_tax_widgets) self.sellable_proxy = self.add_proxy(self._sellable, SellableEditor.sellable_widgets) self.update_requires_weighing_label() def _run_category_editor(self, category=None): self.store.savepoint('before_run_editor_sellable_category') model = run_dialog(SellableCategoryEditor, self, self.store, category) if model: self._fill_categories() self.category_combo.select(model) else: self.store.rollback_to_savepoint('before_run_editor_sellable_category') # # Kiwi handlers # def _on_delete_button__clicked(self, button, parent_button_label=None): sellable_description = self._sellable.get_description() msg = (_("This will delete '%s' from the database. Are you sure?") % sellable_description) if not yesno(msg, gtk.RESPONSE_NO, _("Delete"), _("Keep")): return try: self._sellable.remove() except IntegrityError as details: warning(_("It was not possible to remove '%s'") % sellable_description, str(details)) return self.confirm() def _on_close_sellable_button__clicked(self, button, parent_button_label=None): msg = (_("Do you really want to close '%s'?\n" "Please note that when it's closed, you won't be able to " "commercialize it anymore.") % self._sellable.get_description()) if not yesno(msg, gtk.RESPONSE_NO, parent_button_label, _("Don't close")): return self._sellable.close() self.confirm() def _on_reopen_sellable_button__clicked(self, button, parent_button_label=None): msg = (_("Do you really want to reopen '%s'?\n" "Note that when it's opened, you will be able to " "commercialize it again.") % self._sellable.get_description()) if not yesno(msg, gtk.RESPONSE_NO, parent_button_label, _("Keep closed")): return self._sellable.set_available() self.confirm() def on_category_combo__content_changed(self, category): self.edit_category.set_sensitive(bool(category.get_selected())) def on_tax_constant__changed(self, combo): self._update_tax_value() def on_unit_combo__changed(self, combo): self.update_requires_weighing_label() def on_sale_price_button__clicked(self, button): self.edit_sale_price() def on_add_category__clicked(self, widget): self._run_category_editor() def on_edit_category__clicked(self, widget): self._run_category_editor(self.category_combo.get_selected()) def on_code__validate(self, widget, value): if not value: return ValidationError(_(u'The code can not be empty.')) if self.model.sellable.check_code_exists(value): return ValidationError(_(u'The code %s already exists.') % value) def on_barcode__validate(self, widget, value): if not value: return if value and len(value) > 14: return ValidationError(_(u'Barcode must have 14 digits or less.')) if self.model.sellable.check_barcode_exists(value): return ValidationError(_('The barcode %s already exists') % value) if self._demo_mode and value not in _DEMO_BAR_CODES: return ValidationError(_("Cannot create new barcodes in " "demonstration mode")) def on_price__validate(self, entry, value): if value <= 0: return ValidationError(_("Price cannot be zero or negative")) def on_cost__validate(self, entry, value): if value <= 0: return ValidationError(_("Cost cannot be zero or negative")) def on_print_labels_clicked(self, button, parent_label_button=None): label_data = run_dialog(PrintLabelEditor, None, self.store, self.model.sellable) if label_data: print_labels(label_data, self.store)
class SellableEditor(BaseEditor): """This is a base class for ProductEditor and ServiceEditor and should be used when editing sellable objects. Note that sellable objects are instances inherited by Sellable.""" # This must be be properly defined in the child classes model_name = None model_type = None gladefile = 'SellableEditor' confirm_widgets = ['description', 'cost', 'price'] ui_form_name = None sellable_tax_widgets = ( 'tax_constant', 'tax_value', ) sellable_widgets = ('code', 'barcode', 'description', 'category_combo', 'cost', 'price', 'statuses_combo', 'default_sale_cfop', 'unit_combo') proxy_widgets = (sellable_tax_widgets + sellable_widgets) def __init__(self, store, model=None, visual_mode=False): from stoqlib.gui.slaves.sellableslave import CategoryPriceSlave is_new = not model self._sellable = None self._demo_mode = sysparam(store).DEMO_MODE self._requires_weighing_text = ( "<b>%s</b>" % api.escape(_("This unit type requires weighing"))) if self.ui_form_name: self.db_form = DatabaseForm(store, self.ui_form_name) else: self.db_form = None BaseEditor.__init__(self, store, model, visual_mode) self.enable_window_controls() if self._demo_mode: self._add_demo_warning() # code suggestion edit_code_product = sysparam(self.store).EDIT_CODE_PRODUCT self.code.set_sensitive(not edit_code_product and not self.visual_mode) if not self.code.read(): code = u'%d' % self._sellable.id self.code.update(code) self.description.grab_focus() self.table.set_focus_chain([ self.code, self.barcode, self.default_sale_cfop, self.description, self.cost_hbox, self.price_hbox, self.consignment_box, self.statuses_combo, self.category_combo, self.tax_hbox, self.unit_combo, ]) print_labels_button = self.add_button('print_labels', gtk.STOCK_PRINT) print_labels_button.connect('clicked', self.on_print_labels_clicked, 'print_labels') label = print_labels_button.get_children()[0] label = label.get_children()[0].get_children()[1] label.set_label(_(u'Print labels')) self.setup_widgets() if not is_new and not self.visual_mode: # Although a sellable can be both removed/closed, we show only one, # to avoid having *lots* of buttons. If it's closed, provide a way # to reopen it, else, show a delete button if it can be removed # or a close button if it can be closed if self._sellable.is_closed(): self._add_reopen_button() elif self._sellable.can_remove(): self._add_delete_button() elif self._sellable.can_close(): self._add_close_button() self.set_main_tab_label(self.model_name) price_slave = CategoryPriceSlave(self.store, self.model.sellable, self.visual_mode) self.add_extra_tab(_(u'Category Prices'), price_slave) self._setup_ui_forms() def _add_demo_warning(self): self.set_message( _("This is a demostration mode of Stoq, you cannot create more than %d products.\n" "To avoid this limitation, enable production mode.") % (_DEMO_PRODUCT_LIMIT)) if self.store.find(Sellable).count() > _DEMO_PRODUCT_LIMIT: self.disable_ok() def _add_extra_button(self, label, stock=None, callback_func=None, connect_on='clicked'): button = self.add_button(label, stock) if callback_func: button.connect(connect_on, callback_func, label) def _add_delete_button(self): self._add_extra_button(_('Remove'), gtk.STOCK_DELETE, self._on_delete_button__clicked) def _add_close_button(self): if self._sellable.product: label = _('Close Product') else: label = _('Close Service') self._add_extra_button(label, None, self._on_close_sellable_button__clicked) def _add_reopen_button(self): if self._sellable.product: label = _('Reopen Product') else: label = _('Reopen Service') self._add_extra_button(label, None, self._on_reopen_sellable_button__clicked) def _setup_ui_forms(self): if not self.db_form: return self.db_form.update_widget(self.code, other=self.code_lbl) self.db_form.update_widget(self.barcode, other=self.barcode_lbl) self.db_form.update_widget(self.category_combo, other=self.category_lbl) # # Public API # def set_main_tab_label(self, tabname): self.sellable_notebook.set_tab_label(self.sellable_tab, gtk.Label(tabname)) def add_extra_tab(self, tabname, tabslave): self.sellable_notebook.set_show_tabs(True) self.sellable_notebook.set_show_border(True) event_box = gtk.EventBox() event_box.show() self.sellable_notebook.append_page(event_box, gtk.Label(tabname)) self.attach_slave(tabname, tabslave, event_box) def set_widget_formats(self): for widget in (self.cost, self.price): widget.set_adjustment( gtk.Adjustment(lower=0, upper=sys.maxint, step_incr=1)) self.requires_weighing_label.set_size("small") self.requires_weighing_label.set_text("") def edit_sale_price(self): sellable = self.model.sellable self.store.savepoint('before_run_editor_sellable_price') result = run_dialog(SellablePriceEditor, self, self.store, sellable) if result: self.sellable_proxy.update('price') else: self.store.rollback_to_savepoint( 'before_run_editor_sellable_price') def setup_widgets(self): raise NotImplementedError def update_requires_weighing_label(self): unit = self._sellable.unit if unit and unit.unit_index == UnitType.WEIGHT: self.requires_weighing_label.set_text(self._requires_weighing_text) else: self.requires_weighing_label.set_text("") def _update_tax_value(self): if not hasattr(self, 'tax_proxy'): return self.tax_proxy.update('tax_constant.tax_value') def get_taxes(self): """Subclasses may override this method to provide a custom tax selection. :returns: a list of tuples containing the tax description and a :class:`stoqlib.domain.sellable.SellableTaxConstant` object. """ return [] def _fill_categories(self): categories = self.store.find(SellableCategory) self.category_combo.prefill( api.for_combo(categories, attr='full_description')) # # BaseEditor hooks # def update_visual_mode(self): self.add_category.set_sensitive(False) self.sale_price_button.set_sensitive(False) def setup_sellable_combos(self): self._fill_categories() self.edit_category.set_sensitive(False) # FIXME: Make the combo a label self.statuses_combo.prefill([(v, k) for k, v in Sellable.statuses.items()]) self.statuses_combo.set_sensitive(False) cfops = self.store.find(CfopData) self.default_sale_cfop.prefill(api.for_combo(cfops, empty='')) self.setup_unit_combo() def setup_unit_combo(self): units = self.store.find(SellableUnit) self.unit_combo.prefill(api.for_combo(units, empty=_('No units'))) def setup_tax_constants(self): taxes = self.get_taxes() self.tax_constant.prefill(taxes) def setup_proxies(self): self.set_widget_formats() self._sellable = self.model.sellable self.add_category.set_tooltip_text(_("Add a new category")) self.edit_category.set_tooltip_text(_("Edit the selected category")) self.setup_sellable_combos() self.setup_tax_constants() self.tax_proxy = self.add_proxy(self._sellable, SellableEditor.sellable_tax_widgets) self.sellable_proxy = self.add_proxy(self._sellable, SellableEditor.sellable_widgets) self.update_requires_weighing_label() def _run_category_editor(self, category=None): self.store.savepoint('before_run_editor_sellable_category') model = run_dialog(SellableCategoryEditor, self, self.store, category) if model: self._fill_categories() self.category_combo.select(model) else: self.store.rollback_to_savepoint( 'before_run_editor_sellable_category') # # Kiwi handlers # def _on_delete_button__clicked(self, button, parent_button_label=None): sellable_description = self._sellable.get_description() msg = (_("This will delete '%s' from the database. Are you sure?") % sellable_description) if not yesno(msg, gtk.RESPONSE_NO, _("Delete"), _("Keep")): return try: self._sellable.remove() except IntegrityError as details: warning( _("It was not possible to remove '%s'") % sellable_description, str(details)) return self.confirm() def _on_close_sellable_button__clicked(self, button, parent_button_label=None): msg = (_("Do you really want to close '%s'?\n" "Please note that when it's closed, you won't be able to " "commercialize it anymore.") % self._sellable.get_description()) if not yesno(msg, gtk.RESPONSE_NO, parent_button_label, _("Don't close")): return self._sellable.close() self.confirm() def _on_reopen_sellable_button__clicked(self, button, parent_button_label=None): msg = (_("Do you really want to reopen '%s'?\n" "Note that when it's opened, you will be able to " "commercialize it again.") % self._sellable.get_description()) if not yesno(msg, gtk.RESPONSE_NO, parent_button_label, _("Keep closed")): return self._sellable.set_available() self.confirm() def on_category_combo__content_changed(self, category): self.edit_category.set_sensitive(bool(category.get_selected())) def on_tax_constant__changed(self, combo): self._update_tax_value() def on_unit_combo__changed(self, combo): self.update_requires_weighing_label() def on_sale_price_button__clicked(self, button): self.edit_sale_price() def on_add_category__clicked(self, widget): self._run_category_editor() def on_edit_category__clicked(self, widget): self._run_category_editor(self.category_combo.get_selected()) def on_code__validate(self, widget, value): if not value: return ValidationError(_(u'The code can not be empty.')) if self.model.sellable.check_code_exists(value): return ValidationError(_(u'The code %s already exists.') % value) def on_barcode__validate(self, widget, value): if not value: return if value and len(value) > 14: return ValidationError(_(u'Barcode must have 14 digits or less.')) if self.model.sellable.check_barcode_exists(value): return ValidationError(_('The barcode %s already exists') % value) if self._demo_mode and value not in _DEMO_BAR_CODES: return ValidationError( _("Cannot create new barcodes in " "demonstration mode")) def on_price__validate(self, entry, value): if value <= 0: return ValidationError(_("Price cannot be zero or negative")) def on_cost__validate(self, entry, value): if value <= 0: return ValidationError(_("Cost cannot be zero or negative")) def on_print_labels_clicked(self, button, parent_label_button=None): label_data = run_dialog(PrintLabelEditor, None, self.store, self.model.sellable) if label_data: print_labels(label_data, self.store)