def _get_columns(self): return [Column('description', title=_(u'Product'), data_type=str, expand=True), Column('ordered', title=_(u'Ordered'), data_type=int), Column('stock', title=_(u'Stock'), data_type=int)]
def run_wizard(cls, parent): """Run the wizard to create a product This will run the wizard and after finishing, ask if the user wants to create another product alike. The product will be cloned and `stoqlib.gui.editors.producteditor.ProductEditor` will run as long as the user chooses to create one alike """ with api.new_store() as store: rv = run_dialog(cls, parent, store) if rv: inner_rv = rv while yesno(_("Would you like to register another product alike?"), gtk.RESPONSE_NO, _("Yes"), _("No")): with api.new_store() as store: template = store.fetch(rv) inner_rv = run_dialog(ProductEditor, parent, store, product_type=template.product_type, template=template) if not inner_rv: break # We are insterested in the first rv that means that at least one # obj was created. return rv
def _check_param_online_services(self): from stoqlib.database.runtime import get_default_store, new_store from stoqlib.lib.parameters import sysparam import gtk sparam = sysparam(get_default_store()) if sparam.ONLINE_SERVICES is None: from kiwi.ui.dialogs import HIGAlertDialog # FIXME: All of this is to avoid having to set markup as the default # in kiwi/ui/dialogs:HIGAlertDialog.set_details, after 1.0 # this can be simplified when we fix so that all descriptions # sent to these dialogs are properly escaped dialog = HIGAlertDialog( parent=None, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_WARNING) dialog.add_button(_("Not right now"), gtk.RESPONSE_NO) dialog.add_button(_("Enable online services"), gtk.RESPONSE_YES) dialog.set_primary(_('Do you want to enable Stoq online services?')) dialog.set_details(PRIVACY_STRING, use_markup=True) dialog.set_default_response(gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() store = new_store() sysparam(store).ONLINE_SERVICES = int(bool(response == gtk.RESPONSE_YES)) store.commit() store.close()
def validate_invoice_number(invoice_number, store): if not 0 < invoice_number <= 999999999: return ValidationError( _("Invoice number must be between 1 and 999999999")) if not store.find(Invoice, invoice_number=invoice_number).is_empty(): return ValidationError(_(u'Invoice number already used.'))
def _write_account_cells(self, sheet, cells): for y, cell in enumerate(cells): sheet.write(2 + y, 0, cell, HEADER_LEFT_STYLE) n_rows = len(cells) sheet.write(n_rows + 2, 0, _(u"Average"), HEADER_LEFT_STYLE) sheet.write(n_rows + 3, 0, _(u"Total"), HEADER_LEFT_STYLE)
def _write_headers(self, sheet, n_columns): for x in range(n_columns): month_name = get_month_names()[x] sheet.write(1, 1 + x, month_name, HEADER_TOP_STYLE) sheet.write(1, n_columns + 1, _(u"Average"), HEADER_TOP_STYLE) sheet.write(1, n_columns + 2, _(u"Total"), HEADER_TOP_STYLE)
def on_return_quantity__validate(self, widget, value): if value < self._original_return_qty: return ValidationError(_(u'Can not decrease this quantity.')) total = value + self.model.sale_quantity if total > self.model.quantity: return ValidationError(_(u'Sale and return quantity is greater ' 'than the total quantity.'))
def on_SalesCancel__activate(self, action): sale_view = self.results.get_selected() can_cancel = api.sysparam.get_bool('ALLOW_CANCEL_LAST_COUPON') # A plugin (e.g. ECF) can avoid the cancelation of a sale # because it wants it to be cancelled using another way if can_cancel and SaleAvoidCancelEvent.emit(sale_view.sale): return store = api.new_store() sale = store.fetch(sale_view.sale) msg_text = _(u"This will cancel the sale, Are you sure?") model = SaleComment(store=store, sale=sale, author=api.get_current_user(store)) retval = self.run_dialog( NoteEditor, store, model=model, attr_name='comment', message_text=msg_text, label_text=_(u"Reason"), mandatory=True, ok_button_label=_(u"Cancel sale"), cancel_button_label=_(u"Don't cancel")) if not retval: store.rollback() return sale.cancel() store.commit(close=True) self.refresh()
def _install_postgres(self): self.wizard.disable_back() self.wizard.disable_next() self.label.set_markup( _("Please wait while the package installation is completing.")) packageinstaller = library.get_resource_filename( 'stoq', 'scripts', 'packageinstaller.py') p = subprocess.Popen( [sys.executable, packageinstaller, 'postgresql', 'postgresql-contrib', 'stoq-server'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() self.wizard.enable_back() if p.returncode == 0: self.done = True self.wizard.enable_next() self.label.set_markup( _("Postgresql installation succeeded. You may now proceed " "to the next step by clicking the <b>Forward</b> button")) elif p.returncode == 11: self.wizard.enable_next() self.label.set_markup( _("Authorization failed, try again or connect to " "another database")) else: warning(_("Something went wrong while trying to install " "the PostgreSQL server.")) self.label.set_markup( _("Sorry, something went wrong while installing PostgreSQL, " "try again manually or go back and configure Stoq to connect " "to another."))
def _maybe_create_database(self): logger.info('_maybe_create_database (db_is_local=%s, enable_production=%s)' % (self.wizard.db_is_local, self.wizard.enable_production)) if self.wizard.db_is_local: self._launch_stoqdbadmin() return elif self.wizard.enable_production: self._launch_stoqdbadmin() return self.wizard.write_pgpass() settings = self.wizard.settings self.wizard.config.load_settings(settings) store = settings.create_super_store() version = get_database_version(store) if version < (9, 1): store.close() error(_("Stoq requires PostgresSQL 9.1 or later, but %s found") % ( ".".join(map(str, version)))) try: check_extensions(store=store) except ValueError: store.close() error(_("Missing PostgreSQL extension on the server, " "please install postgresql-contrib on it")) store.close() self.process_view.feed("** Creating database\r\n") self._launch_stoqdbadmin()
def on_SalesCancel__activate(self, action): sale_view = self.results.get_selected() can_cancel = api.sysparam.get_bool('ALLOW_CANCEL_LAST_COUPON') if can_cancel and ECFIsLastSaleEvent.emit(sale_view.sale): info(_("That is last sale in ECF. Return using the menu " "ECF - Cancel Last Document")) return store = api.new_store() sale = store.fetch(sale_view.sale) msg_text = _(u"This will cancel the sale, Are you sure?") model = SaleComment(store=store, sale=sale, author=api.get_current_user(store)) retval = self.run_dialog( NoteEditor, store, model=model, attr_name='comment', message_text=msg_text, label_text=_(u"Reason"), mandatory=True, ok_button_label=_(u"Cancel sale"), cancel_button_label=_(u"Don't cancel")) if not retval: store.rollback() return sale.cancel() store.commit(close=True) self.refresh()
def _get_lonely_payments_columns(self): return [IdentifierColumn('identifier', title=_('Payment #'), sorted=True), Column('method', title=_('Method'), data_type=str), Column('description', title=_('Description'), expand=True, data_type=str), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Payment Value'), data_type=currency)]
def _get_sales_columns(self): return [IdentifierColumn('identifier', title=_('Sale #'), sorted=True), Column('salesperson', title=_('Sales Person'), data_type=str), Column('client', title=_('Client'), data_type=str, expand=True), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Value'), data_type=str, justify=Gtk.Justification.RIGHT)]
def _get_new_value(self): operation = self._entry.get_text().strip() operation = operation.replace(',', '.') if operation.endswith('%'): op_value = operation[:-1] percentage = True else: op_value = operation percentage = False if not operation: return if operation[0] in ['+', '-']: raise ValueError(_("Operator signals are not supported...")) if self._mode == self.MODE_SUB: op = operator.sub elif self._mode == self.MODE_ADD: op = operator.add try: op_value = decimal.Decimal(op_value) except decimal.InvalidOperation: raise ValueError( _("'%s' is not a valid operation...") % (operation,)) if percentage: value = op(self._new_value, self._new_value * (op_value / 100)) else: value = op(self._new_value, op_value) return value
def _maybe_create_database(self): logger.info('_maybe_create_database (db_is_local=%s, remove_demo=%s)' % (self.wizard.db_is_local, self.wizard.remove_demo)) if self.wizard.db_is_local: self._launch_stoqdbadmin() return elif self.wizard.remove_demo: self._launch_stoqdbadmin() return self.wizard.write_pgpass() settings = self.wizard.settings self.wizard.config.load_settings(settings) # store = settings.get_super_store() # version = store.dbVersion() # if version < (8, 1): # info(_("Stoq requires PostgresSQL 8.1 or later, but %s found") % # ".".join(map(str, version))) # store.close() # return False # Secondly, ask the user if he really wants to create the database, dbname = settings.dbname if yesno(_(u"The specified database '%s' does not exist.\n" u"Do you want to create it?") % dbname, gtk.RESPONSE_YES, _(u"Create database"), _(u"Don't create")): self.process_view.feed("** Creating database\r\n") self._launch_stoqdbadmin() else: self.process_view.feed("** Not creating database\r\n") self.wizard.disable_next()
def _check_branch(self): from stoqlib.database.runtime import (get_default_store, new_store, get_current_station, set_current_branch_station) from stoqlib.domain.person import Company from stoqlib.lib.parameters import sysparam from stoqlib.lib.message import info default_store = get_default_store() compaines = default_store.find(Company) if (compaines.count() == 0 or not sysparam.has_object('MAIN_COMPANY')): from stoqlib.gui.base.dialogs import run_dialog from stoqlib.gui.dialogs.branchdialog import BranchDialog if self._ran_wizard: info(_("You need to register a company before start using Stoq")) else: info(_("Could not find a company. You'll need to register one " "before start using Stoq")) store = new_store() person = run_dialog(BranchDialog, None, store) if not person: raise SystemExit branch = person.branch sysparam.set_object(store, 'MAIN_COMPANY', branch) current_station = get_current_station(store) if current_station is not None: current_station.branch = branch store.commit() store.close() set_current_branch_station(default_store, station_name=None)
def _check_param_online_services(self): from stoqlib.database.runtime import new_store from stoqlib.lib.parameters import sysparam from gi.repository import Gtk if sysparam.get_bool('ONLINE_SERVICES') is None: from kiwi.ui.dialogs import HIGAlertDialog # FIXME: All of this is to avoid having to set markup as the default # in kiwi/ui/dialogs:HIGAlertDialog.set_details, after 1.0 # this can be simplified when we fix so that all descriptions # sent to these dialogs are properly escaped dialog = HIGAlertDialog( parent=None, flags=Gtk.DialogFlags.MODAL, type=Gtk.MessageType.WARNING) dialog.add_button(_("Not right now"), Gtk.ResponseType.NO) dialog.add_button(_("Enable online services"), Gtk.ResponseType.YES) dialog.set_primary(_('Do you want to enable Stoq online services?')) dialog.set_details(PRIVACY_STRING, use_markup=True) dialog.set_default_response(Gtk.ResponseType.YES) response = dialog.run() dialog.destroy() store = new_store() sysparam.set_bool(store, 'ONLINE_SERVICES', response == Gtk.ResponseType.YES) store.commit() store.close()
def print_quote_details(self, model, payments_created=False): msg = _('Would you like to print the quote details now?') # We can only print the details if the quote was confirmed. if yesno(msg, gtk.RESPONSE_YES, _("Print quote details"), _("Don't print")): orders = WorkOrder.find_by_sale(self.model.store, self.model) print_report(OpticalWorkOrderReceiptReport, list(orders))
def _remove_work_order(self, holder, name, work_order, work_order_id): if work_order.is_finished(): warning(_("You cannot remove workorder with the status '%s'") % work_order.status_str) return if not work_order.get_items().find().is_empty(): warning(_("This workorder already has items and cannot be removed")) return # We cannot remove the WO from the database (since it already has some # history), but we can disassociate it from the sale, cancel and leave # a reason for it. reason = (_(u'Removed from sale %s') % work_order.sale.identifier) work_order.sale = None work_order.cancel(reason=reason) self._work_order_ids.remove(work_order_id) # Remove the tab self.detach_slave(name) pagenum = self.work_orders_nb.page_num(holder) self.work_orders_nb.remove_page(pagenum) # And remove the WO self.wizard.workorders.remove(work_order) self.force_validation()
def _update_filter_items(self): options = [ FilterItem(_('Received payments'), 'status:paid'), FilterItem(_('To receive'), 'status:not-paid'), FilterItem(_('Late payments'), 'status:late'), ] self.add_filter_items(PaymentCategory.TYPE_RECEIVABLE, options)
def get_subtitle(self): """Returns a subtitle text """ if self.end_date: return _('Till movement on %s to %s') % (self.start_date, self.end_date) return _('Till movement on %s') % self.start_date
def get_descriptions(self): app_desc = [] for name, (label, description) in sorted(_APPLICATIONS.items()): icon = get_application_icon(name) app_desc.append((name, _(label), icon, _(description))) return app_desc
def create_ui(self): if api.sysparam(self.store).SMART_LIST_LOADING: self.search.enable_lazy_search() self.popup = self.uimanager.get_widget('/StockSelection') self.window.add_new_items([self.NewReceiving, self.NewTransfer, self.NewStockDecrease, self.LoanNew]) self.window.add_search_items([ self.SearchStockItems, self.SearchStockDecrease, self.SearchClosedStockItems, self.SearchProductHistory, self.SearchPurchasedStockItems, self.SearchTransfer, ]) self.window.Print.set_tooltip( _("Print a report of these products")) self._inventory_widgets = [self.NewTransfer, self.NewReceiving, self.StockInitial, self.NewStockDecrease, self.LoanNew, self.LoanClose] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) self.image_viewer = None self.image = gtk.Image() self.edit_button = self.uimanager.get_widget('/toolbar/AppToolbarPH/EditProduct') self.edit_button.set_icon_widget(self.image) self.image.show() self.search.set_summary_label(column='stock', label=_('<b>Stock Total:</b>'), format='<b>%s</b>', parent=self.get_statusbar_message_area())
def _till_status_changed(self, closed, blocked): def large(s): return '<span weight="bold" size="xx-large">%s</span>' % ( api.escape(s), ) if closed: text = large(_("Till closed")) if not blocked: text += '\n\n<span size="large"><a href="open-till">%s</a></span>' % ( api.escape(_('Open till'))) elif blocked: text = large(_("Till blocked")) else: text = large(_("Till open")) self.till_status_label.set_use_markup(True) self.till_status_label.set_justify(gtk.JUSTIFY_CENTER) self.till_status_label.set_markup(text) self.set_sensitive([self.TillOpen], closed) self.set_sensitive([self.TillVerify], not closed and not blocked) self.set_sensitive([ self.TillClose, self.NewTrade, self.LoanClose, self.WorkOrderClose ], not closed or blocked) self._set_sale_sensitive(not closed and not blocked)
def _add_sellable(self, sellable, batch=None): quantity = self._read_quantity() if quantity == 0: return if not sellable.is_valid_quantity(quantity): warning( _(u"You cannot sell fractions of this product. " u"The '%s' unit does not allow that") % sellable.unit_description) return if sellable.product: # If the sellable has a weight unit specified and we have a scale # configured for this station, go and check what the scale says. if (sellable and sellable.unit and sellable.unit.unit_index == UnitType.WEIGHT and self._scale_settings): self._read_scale(sellable) storable = sellable.product_storable if storable is not None: if not self._check_available_stock(storable, sellable): info( _("You cannot sell more items of product %s. " "The available quantity is not enough.") % sellable.get_description()) self.barcode.set_text('') self.barcode.grab_focus() return self._update_list(sellable, batch=batch) self.barcode.grab_focus()
def on_NewTrade__activate(self, action): if self._trade: if yesno( _("There is already a trade in progress... Do you " "want to cancel it and start a new one?"), gtk.RESPONSE_NO, _("Cancel trade"), _("Finish trade")): self._clear_trade(remove=True) else: return if self._current_store: store = self._current_store store.savepoint('before_run_wizard_saletrade') else: store = api.new_store() trade = self.run_dialog(SaleTradeWizard, store) if trade: self._trade = trade self._current_store = store elif self._current_store: store.rollback_to_savepoint('before_run_wizard_saletrade') else: store.rollback(close=True) self._show_trade_infobar(trade)
def _get_account_columns(self): def format_withdrawal(value): if value < 0: return currency(abs(value)).format(symbol=True, precision=2) def format_deposit(value): if value > 0: return currency(value).format(symbol=True, precision=2) if self.model.account_type == Account.TYPE_INCOME: color_func = lambda x: False else: color_func = lambda x: x < 0 return [Column('date', title=_("Date"), data_type=datetime.date, sorted=True), Column('code', title=_("Code"), data_type=unicode), Column('description', title=_("Description"), data_type=unicode, expand=True), Column('account', title=_("Account"), data_type=unicode), Column('value', title=self.model.account.get_type_label(out=False), data_type=currency, format_func=format_deposit), Column('value', title=self.model.account.get_type_label(out=True), data_type=currency, format_func=format_withdrawal), ColoredColumn('total', title=_("Total"), data_type=currency, color='red', data_func=color_func)]
def get_columns(self): return [ Column( 'code', title=_('Reference'), data_type=str, width=130, justify=gtk.JUSTIFY_RIGHT), Column( 'full_description', title=_('Description'), data_type=str, expand=True, searchable=True, ellipsize=pango.ELLIPSIZE_END), Column( 'price', title=_('Price'), data_type=currency, width=110, justify=gtk.JUSTIFY_RIGHT), Column( 'quantity_unit', title=_('Quantity'), data_type=unicode, width=110, justify=gtk.JUSTIFY_RIGHT), Column( 'total', title=_('Total'), data_type=currency, justify=gtk.JUSTIFY_RIGHT, width=100) ]
def _update_till_status(self, closed, blocked): # Three different situations; # # - Till is closed # - Till is opened # - Till was not closed the previous fiscal day (blocked) self.set_sensitive([self.TillOpen], closed) self.set_sensitive([self.TillClose, self.PaymentReceive], not closed or blocked) widgets = [self.TillVerify, self.TillAddCash, self.TillRemoveCash, self.SearchTillHistory, self.app_vbox] self.set_sensitive(widgets, not closed and not blocked) if closed: text = _(u"Till closed") self.clear() self.setup_focus() elif blocked: text = _(u"Till blocked from previous day") else: till = Till.get_current(self.store) text = _(u"Till opened on %s") % till.opening_date.strftime('%x') self.till_status_label.set_text(text) self._update_toolbar_buttons() self._update_total()
def on_quantity__validate(self, widget, value): if self._expanded_edition: return if value <= 0: return ValidationError(_(u'The quantity should be positive.')) if value and not self._has_stock(value): return ValidationError(_(u'Quantity not available in stock.'))
def get_description(self): return _('%s (upid: %s)') % (self.person.name, self.crm_number)
class OpticalPatientMeasures(Domain): __storm_table__ = 'optical_patient_measures' EYE_LEFT = u'left' EYE_RIGHT = u'right' eye_options = { EYE_LEFT: _('Left Eye'), EYE_RIGHT: _('Right Eye'), } create_date = DateTimeCol(default_factory=StatementTimestamp) client_id = IdCol(allow_none=False) #: The related client client = Reference(client_id, 'Client.id') responsible_id = IdCol(allow_none=False) #: The user that registred this information responsible = Reference(responsible_id, 'LoginUser.id') dominant_eye = EnumCol(allow_none=False, default=EYE_LEFT) le_keratometer_horizontal = UnicodeCol() le_keratometer_vertical = UnicodeCol() le_keratometer_axis = UnicodeCol() re_keratometer_horizontal = UnicodeCol() re_keratometer_vertical = UnicodeCol() re_keratometer_axis = UnicodeCol() le_eyebrown = UnicodeCol() le_eyelash = UnicodeCol() le_conjunctiva = UnicodeCol() le_sclerotic = UnicodeCol() le_iris_diameter = UnicodeCol() le_eyelid = UnicodeCol() le_eyelid_opening = UnicodeCol() le_cornea = UnicodeCol() #: Tear breakup time. How much time the eye takes to produce a tear le_tbut = UnicodeCol() #: test that checks how much tear the eye produces le_schirmer = UnicodeCol() re_eyebrown = UnicodeCol() re_eyelash = UnicodeCol() re_conjunctiva = UnicodeCol() re_sclerotic = UnicodeCol() re_iris_diameter = UnicodeCol() re_eyelid = UnicodeCol() re_eyelid_opening = UnicodeCol() re_cornea = UnicodeCol() #: Tear breakup time. How much time the eye takes to produce a tear re_tbut = UnicodeCol() #: test that checks how much tear the eye produces re_schirmer = UnicodeCol() notes = UnicodeCol() @property def responsible_name(self): return self.responsible.get_description()
class OpticalPatientHistory(Domain): __storm_table__ = 'optical_patient_history' #: Never used lenses before TYPE_FIRST_USER = u'first-user' #: Is currently a user TYPE_SECOND_USER = u'second-user' #: Has used lenses before, but stopped TYPE_EX_USER = u'ex-user' user_types = collections.OrderedDict([ (TYPE_FIRST_USER, _('First User')), (TYPE_SECOND_USER, _('Second User')), (TYPE_EX_USER, _('Ex-User')), ]) create_date = DateTimeCol(default_factory=StatementTimestamp) client_id = IdCol(allow_none=False) #: The related client client = Reference(client_id, 'Client.id') responsible_id = IdCol(allow_none=False) #: The user that registred this information responsible = Reference(responsible_id, 'LoginUser.id') # # Section 1: General questions # #: If the patient is a first time user for contact lenses or not. user_type = EnumCol(allow_none=False, default=TYPE_FIRST_USER) #: What is the occupation of the patient occupation = UnicodeCol() #: Details about the work environment (if it as air conditioning, dust, #: chemical products) work_environment = UnicodeCol() # # First time user # #: If the patient has ever tested any contact lenses has_tested = UnicodeCol() #: What brands the patient has tested tested_brand = UnicodeCol() #: If previous tests irritated the eye eye_irritation = UnicodeCol() #: What is the main purpose for using contact lenses? purpose_of_use = UnicodeCol() #: How many hours per day the patient intends to use the contact lenses intended_hour_usage = UnicodeCol() # # Second time / ex user # #: Previous brand of the client. previous_brand = UnicodeCol() #: What the previous brand felt like previous_feeling = UnicodeCol() #: Have ever had any cornea issues cornea_issues = UnicodeCol() #: How many hours per day the client used the lenses hours_per_day_usage = UnicodeCol() # # Second time user # #: For how long is a user user_since = UnicodeCol() #: Bring the previous lenses? has_previous_lenses = UnicodeCol() #: Previous lenses observations previous_lenses_notes = UnicodeCol() # # Ex User # #: How long since the last use. last_use = UnicodeCol() #: why stopped using stop_reason = UnicodeCol() #: Did frequent removal of proteins? protein_removal = UnicodeCol() #: What cleaning product used? cleaning_product = UnicodeCol() #: Free notes. history_notes = UnicodeCol() # # Section 2: Adaptation test # #: If the patient ever had eye injuries eye_injury = UnicodeCol() #: Any kind of recent pathology, like pink-eye recent_pathology = UnicodeCol() #: Is currently using eye drops using_eye_drops = UnicodeCol() #: Does the patient have health problems health_problems = UnicodeCol() #: Is the patient is using any kind of medicament using_medicament = UnicodeCol() #: Does the patient family has any health problems family_health_problems = UnicodeCol() #: How the eyes feel at the end of the day (burn, itch, etc...) end_of_day_feeling = UnicodeCol() #: Free notes. adaptation_notes = UnicodeCol() @property def responsible_name(self): return self.responsible.get_description()
class OpticalWorkOrder(Domain): """This holds the necessary information to execute an work order for optical stores. This includes all the details present in the prescription. For reference: http://en.wikipedia.org/wiki/Eyeglass_prescription See http://en.wikipedia.org/wiki/Eyeglass_prescription#Abbreviations_and_terms for reference no the names used here. In some places, RE is used as a short for right eye, and LE for left eye """ __storm_table__ = 'optical_work_order' #: Lens used in glasses LENS_TYPE_OPHTALMIC = u'ophtalmic' #: Contact lenses LENS_TYPE_CONTACT = u'contact' #: The frame for the lens is a closed ring FRAME_TYPE_CLOSED_RING = u'closed-ring' #: The frame uses a nylon string to hold the lenses. FRAME_TYPE_NYLON = u'nylon' #: The frame is made 3 pieces FRAME_TYPE_3_PIECES = u'3-pieces' lens_types = { LENS_TYPE_OPHTALMIC: _('Ophtalmic'), LENS_TYPE_CONTACT: _('Contact'), } frame_types = { # Translators: Aro fechado FRAME_TYPE_3_PIECES: _('Closed ring'), # Translators: Fio de nylon FRAME_TYPE_NYLON: _('Nylon String'), # Translators: 3 preças FRAME_TYPE_CLOSED_RING: _('3 pieces'), } work_order_id = IdCol(allow_none=False) work_order = Reference(work_order_id, 'WorkOrder.id') medic_id = IdCol() medic = Reference(medic_id, 'OpticalMedic.id') prescription_date = DateTimeCol() #: The name of the patient. Note that we already have the client of the work #: order, but the patient may be someone else (like the son, father, #: etc...). Just the name is enough patient = UnicodeCol() #: The type of the lens, Contact or Ophtalmic lens_type = EnumCol(default=LENS_TYPE_OPHTALMIC) # # Frame # #: The type of the frame. One of OpticalWorkOrder.FRAME_TYPE_* frame_type = EnumCol(default=FRAME_TYPE_CLOSED_RING) #: The vertical frame measure frame_mva = DecimalCol(default=decimal.Decimal(0)) #: The horizontal frame measure frame_mha = DecimalCol(default=decimal.Decimal(0)) #: The diagonal frame measure frame_mda = DecimalCol(default=decimal.Decimal(0)) #: The brige is the part of the frame between the two lenses, above the nose. frame_bridge = DecimalCol() # # Left eye distance vision # le_distance_spherical = DecimalCol(default=0) le_distance_cylindrical = DecimalCol(default=0) le_distance_axis = DecimalCol(default=0) le_distance_prism = DecimalCol(default=0) le_distance_base = DecimalCol(default=0) le_distance_height = DecimalCol(default=0) #: Pupil distance (DNP in pt_BR) le_distance_pd = DecimalCol(default=0) le_addition = DecimalCol(default=0) # # Left eye distance vision # le_near_spherical = DecimalCol(default=0) le_near_cylindrical = DecimalCol(default=0) le_near_axis = DecimalCol(default=0) #: Pupil distance (DNP in pt_BR) le_near_pd = DecimalCol(default=0) # # Right eye distance vision # re_distance_spherical = DecimalCol(default=0) re_distance_cylindrical = DecimalCol(default=0) re_distance_axis = DecimalCol(default=0) re_distance_prism = DecimalCol(default=0) re_distance_base = DecimalCol(default=0) re_distance_height = DecimalCol(default=0) #: Pupil distance (DNP in pt_BR) re_distance_pd = DecimalCol(default=0) re_addition = DecimalCol(default=0) # # Right eye near vision # re_near_spherical = DecimalCol(default=0) re_near_cylindrical = DecimalCol(default=0) re_near_axis = DecimalCol(default=0) #: Pupil distance (DNP in pt_BR) re_near_pd = DecimalCol(default=0) # # Class methods # @classmethod def find_by_work_order(cls, store, work_order): return store.find(cls, work_order_id=work_order.id).one() # # Properties # @property def frame_type_str(self): return self.frame_types.get(self.frame_type, '') @property def lens_type_str(self): return self.lens_types.get(self.lens_type, '') # # Public API # def can_create_purchase(self): work_order = self.work_order if work_order.status != WorkOrder.STATUS_WORK_IN_PROGRESS: return False if not work_order.sale: return False purchases = [i.purchase_item for i in work_order.get_items()] # If there are any item in this work order that was not purchased yet, then we # can still create a purchase return None in purchases def create_purchase(self, supplier, work_order_item, is_freebie): """Create a purchase :param supplier: the |supplier| of that purchase :param work_order_item: The work order item that a purchase is being created for. :param is_freebie: indicates if the item is a freebie """ sellable = work_order_item.sellable store = self.work_order.store current_branch = api.get_current_branch(store) purchase = PurchaseOrder(store=store, status=PurchaseOrder.ORDER_PENDING, supplier=supplier, responsible=api.get_current_user(store), branch=current_branch, work_order=self.work_order) if is_freebie: purchase.notes = _('The product %s is a freebie') % sellable.description # FIXME We may want the cost 0, but as it is we wont be able to # receive this purchase without changing the receiving. We must # evaluate the consequences of changing the receiving a little bit # further in order to change that behavior. cost = decimal.Decimal('0.01') else: psi = ProductSupplierInfo.find_by_product_supplier(store, sellable.product, supplier) cost = psi.base_cost if psi else sellable.cost # Add the sellable to the purchase purchase_item = purchase.add_item(sellable, quantity=work_order_item.quantity, cost=cost) work_order_item.purchase_item = purchase_item purchase.confirm() return purchase def can_receive_purchase(self, purchase): work_order = self.work_order if not work_order.status == WorkOrder.STATUS_WORK_FINISHED: return False # XXX Lets assume that there is only on purchase return purchase and purchase.status == PurchaseOrder.ORDER_CONFIRMED def receive_purchase(self, purchase_order, reserve=False): receiving = purchase_order.create_receiving_order() receiving.confirm() if reserve: self.reserve_products(purchase_order) def reserve_products(self, purchase_order): for item in self.work_order.get_items(): if not item.purchase_item: continue sale_item = item.sale_item to_reserve = sale_item.quantity - sale_item.quantity_decreased if to_reserve > 0: sale_item.reserve(quantize(to_reserve)) def copy(self, target): """Make a copy of self into a target |work_order| :param target: a |work_order| """ props = ['lens_type', 'le_distance_spherical', 'le_distance_cylindrical', 'le_distance_axis', 'le_distance_prism', 'le_distance_base', 'le_distance_height', 'le_distance_pd', 'le_addition', 'le_near_spherical', 'le_near_cylindrical', 'le_near_axis', 'le_near_pd', 're_distance_spherical', 're_distance_cylindrical', 're_distance_axis', 're_distance_prism', 're_distance_base', 're_distance_height', 're_distance_pd', 're_addition', 're_near_spherical', 're_near_cylindrical', 're_near_axis', 're_near_pd'] for prop in props: value = getattr(self, prop) setattr(target, prop, value)
def refresh(self): if stoq.trial_mode: self.status = ResourceStatus.STATUS_NA self.reason = (_('Online features are not available in trial mode')) self.reason_long = _('Online features require a subscription of Stoq.link') return if not api.sysparam.get_bool('ONLINE_SERVICES'): self.status = ResourceStatus.STATUS_NA self.reason = _('Backup service not running because ' '"Online Services" is disabled') self.reason_long = _('Enable the parameter "Online Services" ' 'on the "Admin" app to solve this issue') return try: key = self._get_key() except ServerError: pass else: if not key: self.status = self.STATUS_WARNING self.reason = _("Backup key not configured") self.reason_long = _('Click on "Configure" button to ' 'configure the backup key') return try: response = self._get_server_status() except Exception as e: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = str(e) return if response.status_code != 200: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = None return data = response.json() if data['latest_backup_date']: backup_date = dateutil.parser.parse(data['latest_backup_date']) delta = datetime.datetime.today() - backup_date if delta.days > 3: self.status = self.STATUS_WARNING self.reason = _("Backup is late. Last backup date is %s") % ( backup_date.strftime('%x')) self.reason_long = _("Check your Stoq Server logs to see if " "there's any problem with it") else: self.status = self.STATUS_OK self.reason = _("Backup is up-to-date. Last backup date is %s") % ( backup_date.strftime('%x')) self.reason_long = None else: self.status = self.STATUS_WARNING self.reason = _("There's no backup data yet") self.reason_long = None
def _on_manager__action_finished(self, manager, action, retval): if isinstance(retval, Exception): warning(_('An error happened when executing "%s"') % (action.label, ), str(retval)) self._update_ui()
class TillDailyMovementDialog(BaseEditor): """Shows informations related to till operations over a daterange. It can also be filtered by branch. """ title = _("Daily Movement") hide_footer = True size = (950, 450) model_type = Settable gladefile = "TillDailyMovementDialog" proxy_widgets = ['branch', 'in_subtotal', 'in_credit', 'in_total', 'out_subtotal', 'out_credit', 'out_total'] # # Private # def _setup_widgets(self): # Branches combo items = api.get_branches_for_filter(self.store) self.branch.prefill(items) # Daterange filter self.date_filter = DateSearchFilter(_(u'Date:')) self.date_filter.clear_options() self.date_filter.add_custom_options() for option in [Today, Yesterday, LastWeek, LastMonth]: self.date_filter.add_option(option) self.date_filter.select(position=0) self.daterange_hbox.pack_start(self.date_filter, False, False, 0) self.date_filter.show_all() # Setting report lists' columns self.sales_list.set_columns(self._get_sales_columns()) self.inpayments_list.set_columns(self._get_lonely_payments_columns()) self.purchases_list.set_columns(self._get_purchases_columns()) self.outpayments_list.set_columns(self._get_lonely_payments_columns()) self.return_sales_list.set_columns(self._get_return_sales_columns()) self.supplies_list.set_columns(self._get_till_columns()) self.removals_list.set_columns(self._get_till_columns()) self.permethod_list.set_columns(self._get_permethod_columns()) self.percard_list.set_columns(self._get_percard_columns()) # Print button is insensitive, until the first report is generated self.print_button.set_sensitive(False) self._setup_summary_labels() def _get_sales_columns(self): return [IdentifierColumn('identifier', title=_('Sale #'), sorted=True), Column('salesperson', title=_('Sales Person'), data_type=str), Column('client', title=_('Client'), data_type=str, expand=True), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Value'), data_type=str, justify=Gtk.Justification.RIGHT)] def _get_lonely_payments_columns(self): return [IdentifierColumn('identifier', title=_('Payment #'), sorted=True), Column('method', title=_('Method'), data_type=str), Column('description', title=_('Description'), expand=True, data_type=str), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Payment Value'), data_type=currency)] def _get_purchases_columns(self): return [IdentifierColumn('identifier', title=_('Code #'), sorted=True), Column('status_str', title=_('Status'), data_type=str), Column('responsible_name', title=_('Responsible'), expand=True, data_type=str), Column('branch_name', title=_('Branch'), data_type=str), Column('notes', title=_('Notes'), data_type=str), Column('supplier_name', title=_('Supplier'), data_type=str), Column('purchase_total', title=_('Value'), data_type=currency)] def _get_return_sales_columns(self): return [IdentifierColumn('identifier', title=_('Code #'), sorted=True), Column('salesperson', title=_('Sales Person'), data_type=str), Column('client', title=_('Client'), expand=True, data_type=str), Column('return_date', title=_('Return Date'), data_type=datetime.date), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Sale Value'), data_type=currency)] def _get_till_columns(self): return [IdentifierColumn('identifier', title=_('Entry #'), sorted=True), Column('description', title=_('Description'), data_type=str, expand=True), Column('branch_name', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Value'), data_type=currency)] def _get_permethod_columns(self): return [Column('method', title=_('Payment Method'), sorted=True, expand=True), Column('in_value', title=_('Income Total'), data_type=currency), Column('out_value', title=_('Outgoing Total'), data_type=currency)] def _get_percard_columns(self): return [Column('provider', title=_('Provider Name'), data_type=str, expand=True), Column('income', title=_('Income Total'), data_type=currency)] def _create_summary_label(self, report, column='value', label=None): # Setting tha data obj_list = getattr(self, report + '_list') box = getattr(self, report + '_vbox') if label is None: label = _('Total:') label = '<b>%s</b>' % api.escape(label) value_format = '<b>%s</b>' # Creating the label label = SummaryLabel(klist=obj_list, column=column, label=label, value_format=value_format) # Displaying the label box.pack_start(label, False, False, 0) label.show() return label def _setup_summary_labels(self): # Supplies self.supplies_label = self._create_summary_label('supplies') # Removals self.removals_label = self._create_summary_label('removals') # Percard self.percard_label = self._create_summary_label('percard', column='income') def _update_summary_labels(self): self.supplies_label.update_total() self.removals_label.update_total() self.percard_label.update_total() self.proxy.update_many(('in_subtotal', 'in_credit', 'in_total', 'out_subtotal', 'out_credit', 'out_total')) def _generate_dailymovement_data(self, store): query = And(Payment.status.is_in([Payment.STATUS_PENDING, Payment.STATUS_PAID]), self._get_query(Payment.open_date, Payment.branch)) # Keys are the sale objects, and values are lists with all payments self.sales = collections.OrderedDict() # Keys are the returned sale objects, and values are lists with all payments self.return_sales = collections.OrderedDict() self.purchases = collections.OrderedDict() # lonely input and output payments self.lonely_in_payments = [] self.lonely_out_payments = [] # values are lists with the first element the summary of the input, and # the second the summary of the output method_summary = {} self.card_summary = {} result = store.find(DailyInPaymentView, query) for p in result.order_by(Sale.identifier, Payment.identifier): if p.sale: subtotal = p.sale_subtotal total = p.sale.get_total_sale_amount(subtotal) salesperson = p.salesperson_name or _('Not Specified') client = p.client_name or _('Not Specified') sale = DailyMovementSale(identifier=p.sale.identifier, salesperson=salesperson, client=client, branch=p.branch_name, value=get_formatted_price(total)) sale_payments = self.sales.setdefault(sale, {}) details = '' method_desc = p.method.get_description() if p.check_data: account = p.check_data.bank_account numbers = [payment.payment_number for payment in p.sale.payments if bool(payment.payment_number)] # Ensure that the check numbers are ordered numbers.sort() parts = [] if account.bank_number: parts.append(_(u'Bank: %s') % account.bank_number) if account.bank_branch: parts.append(_(u'Agency: %s') % account.bank_branch) if account.bank_account: parts.append(_(u'Account: %s') % account.bank_account) if numbers: parts.append(_(u'Numbers: %s') % ', '.join(numbers)) details = ' / '.join(parts) if p.card_data: if p.card_data.card_type == CreditCardData.TYPE_DEBIT: method_desc += ' ' + _('Debit') else: method_desc += ' ' + _(u'Credit') details = '%s - %s - %s' % (p.card_data.auth, p.card_data.provider.short_name or '', p.card_data.device.description or '') key = (method_desc, details) item = sale_payments.setdefault(key, [0, 0]) item[0] += p.value item[1] += 1 else: self.lonely_in_payments.append(p) method_summary.setdefault(p.method, [0, 0]) method_summary[p.method][0] += p.value if p.card_data: type_desc = p.card_data.short_desc[p.card_data.card_type] key = (p.card_data.provider.short_name, type_desc) self.card_summary.setdefault(key, 0) self.card_summary[key] += p.value result = store.find(DailyOutPaymentView, query) for p in result.order_by(Payment.identifier): if p.purchase: purchase_payments = self.purchases.setdefault(p.purchase, []) purchase_payments.append(p) elif p.sale: subtotal = p.sale_subtotal value = p.sale.get_total_sale_amount(subtotal) salesperson = p.salesperson_name or _('Not Specified') client = p.client_name or _('Not Specified') sale = DailyMovementSale(identifier=p.sale.identifier, salesperson=salesperson, client=client, return_date=p.sale.return_date, branch=p.branch_name, value=value) return_sales_payment = self.return_sales.setdefault(sale, []) return_sales_payment.append(p) else: self.lonely_out_payments.append(p) method_summary.setdefault(p.method, [0, 0]) method_summary[p.method][1] += p.value self.method_summary = [] for method, (in_value, out_value) in method_summary.items(): self.method_summary.append((method, in_value, out_value)) self.method_summary.sort(key=lambda m: _(m[0].description)) # Till removals query = And(Eq(TillEntry.payment_id, None), self._get_query(TillEntry.date, TillEntry.branch), TillEntry.value < 0) self.till_removals = store.find(TillEntry, query) # Till supply query = And(Eq(TillEntry.payment_id, None), self._get_query(TillEntry.date, TillEntry.branch), TillEntry.value > 0) self.till_supplies = store.find(TillEntry, query) def _show_lonely_payments(self, payments, widget): widget.clear() for payment in payments: payment_data = Settable(identifier=payment.identifier, method=payment.method.get_description(), description=payment.description, branch=payment.branch_name, value=payment.value) widget.append(payment_data) def _show_report(self): self._generate_dailymovement_data(self.store) # Sale data self.sales_list.clear() for sale, payments in self.sales.items(): self.sales_list.append(None, sale) for details, values in payments.items(): value = '%s (%sx)' % (get_formatted_price(values[0]), values[1]) payment_data = Settable(identifier=None, salesperson=details[0], client=details[1], value=value) self.sales_list.append(sale, payment_data) # Lonely in payments self._show_lonely_payments(self.lonely_in_payments, self.inpayments_list) # Purchase data self.purchases_list.clear() for purchase, payments in self.purchases.items(): self.purchases_list.append(None, purchase) for payment in payments: # TODO Add details refering to Bank, Agency later payment_data = Settable(identifier=payment.identifier, notes=payment.method.get_description()) self.purchases_list.append(purchase, payment_data) # Lonely out payments self._show_lonely_payments(self.lonely_out_payments, self.outpayments_list) # Return sales self.return_sales_list.clear() for sale, payments in self.return_sales.items(): self.return_sales_list.append(None, sale) for payment in payments: payment_data = Settable(identifier=payment.identifier, salesperson=payment.method.get_description(), client=payment.description, value=get_formatted_price(payment.value)) self.return_sales_list.append(sale, payment_data) # Supplies self.supplies_list.clear() self.supplies_list.add_list(self.till_supplies) # Removals self.removals_list.clear() self.removals_list.add_list(self.till_removals) # Summary's per payment method data self.permethod_list.clear() self.model.in_subtotal = self.model.out_subtotal = 0 self.model.in_credit = self.model.out_credit = currency(0) for method in self.method_summary: method_data = Settable(method=_(method[0].description), in_value=method[1], out_value=method[2]) self.permethod_list.append(method_data) self.model.in_subtotal += method[1] self.model.out_subtotal += method[2] if method[0].method_name == 'credit': self.model.in_credit = currency(method[1]) self.model.out_credit = currency(method[2]) self.model.in_subtotal = currency(self.model.in_subtotal) self.model.out_subtotal = currency(self.model.out_subtotal) self.model.in_total = currency(self.model.in_subtotal - self.model.in_credit) self.model.out_total = currency(self.model.out_subtotal - self.model.out_credit) # Summary's per card provider data self.percard_list.clear() keys = list(self.card_summary.keys()) for key in sorted(keys): card_summary_data = Settable(provider=key[0] + ' ' + key[1], income=self.card_summary[key]) self.percard_list.append(card_summary_data) self._update_summary_labels() def _get_query(self, date_attr, branch_attr): daterange = self.get_daterange() query = [Date(date_attr) >= Date(daterange[0]), Date(date_attr) <= Date(daterange[1])] branch = self.model.branch if branch is not None: query.append(branch_attr == branch) return And(*query) # # Public API # def get_daterange(self): start = self.date_filter.get_start_date() end = self.date_filter.get_end_date() return (start, end) def set_daterange(self, start, end=None): self.date_filter.set_state(start, end) # # BaseEditor Hooks # def create_model(self, store): return Settable(branch=api.get_current_branch(store), in_total=currency(0), in_credit=currency(0), in_subtotal=currency(0), out_total=currency(0), out_credit=currency(0), out_subtotal=currency(0)) def setup_proxies(self): self._setup_widgets() self.proxy = self.add_proxy(self.model, TillDailyMovementDialog.proxy_widgets) # # Callbacks # def on_search_button__clicked(self, widget): self._show_report() self.print_button.set_sensitive(True) def on_print_button__clicked(self, widget): branch = self.model.branch daterange = self.get_daterange() print_report(TillDailyMovementReport, self.store, branch, daterange, self)
def _show_report(self): self._generate_dailymovement_data(self.store) # Sale data self.sales_list.clear() for sale, payments in self.sales.items(): self.sales_list.append(None, sale) for details, values in payments.items(): value = '%s (%sx)' % (get_formatted_price(values[0]), values[1]) payment_data = Settable(identifier=None, salesperson=details[0], client=details[1], value=value) self.sales_list.append(sale, payment_data) # Lonely in payments self._show_lonely_payments(self.lonely_in_payments, self.inpayments_list) # Purchase data self.purchases_list.clear() for purchase, payments in self.purchases.items(): self.purchases_list.append(None, purchase) for payment in payments: # TODO Add details refering to Bank, Agency later payment_data = Settable(identifier=payment.identifier, notes=payment.method.get_description()) self.purchases_list.append(purchase, payment_data) # Lonely out payments self._show_lonely_payments(self.lonely_out_payments, self.outpayments_list) # Return sales self.return_sales_list.clear() for sale, payments in self.return_sales.items(): self.return_sales_list.append(None, sale) for payment in payments: payment_data = Settable(identifier=payment.identifier, salesperson=payment.method.get_description(), client=payment.description, value=get_formatted_price(payment.value)) self.return_sales_list.append(sale, payment_data) # Supplies self.supplies_list.clear() self.supplies_list.add_list(self.till_supplies) # Removals self.removals_list.clear() self.removals_list.add_list(self.till_removals) # Summary's per payment method data self.permethod_list.clear() self.model.in_subtotal = self.model.out_subtotal = 0 self.model.in_credit = self.model.out_credit = currency(0) for method in self.method_summary: method_data = Settable(method=_(method[0].description), in_value=method[1], out_value=method[2]) self.permethod_list.append(method_data) self.model.in_subtotal += method[1] self.model.out_subtotal += method[2] if method[0].method_name == 'credit': self.model.in_credit = currency(method[1]) self.model.out_credit = currency(method[2]) self.model.in_subtotal = currency(self.model.in_subtotal) self.model.out_subtotal = currency(self.model.out_subtotal) self.model.in_total = currency(self.model.in_subtotal - self.model.in_credit) self.model.out_total = currency(self.model.out_subtotal - self.model.out_credit) # Summary's per card provider data self.percard_list.clear() keys = list(self.card_summary.keys()) for key in sorted(keys): card_summary_data = Settable(provider=key[0] + ' ' + key[1], income=self.card_summary[key]) self.percard_list.append(card_summary_data) self._update_summary_labels()
def _get_percard_columns(self): return [Column('provider', title=_('Provider Name'), data_type=str, expand=True), Column('income', title=_('Income Total'), data_type=currency)]
def _get_permethod_columns(self): return [Column('method', title=_('Payment Method'), sorted=True, expand=True), Column('in_value', title=_('Income Total'), data_type=currency), Column('out_value', title=_('Outgoing Total'), data_type=currency)]
def _get_till_columns(self): return [IdentifierColumn('identifier', title=_('Entry #'), sorted=True), Column('description', title=_('Description'), data_type=str, expand=True), Column('branch_name', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Value'), data_type=currency)]
class StatusDialog(BasicDialog): size = (700, 400) title = _("System Status") def __init__(self, *args, **kwargs): kwargs.setdefault('size', self.size) kwargs.setdefault('title', self.title) super(StatusDialog, self).__init__(*args, **kwargs) self._manager = ResourceStatusManager.get_instance() self._manager.connect('status-changed', self._on_manager__status_changed) self._manager.connect('action-finished', self._on_manager__action_finished) with api.new_store() as store: user = api.get_current_user(store) self._is_admin = user.profile.check_app_permission(u'admin') self._widgets = {} self._setup_ui() # # Private # def _setup_ui(self): self.cancel_button.hide() for child in self.vbox.get_children(): self.vbox.remove(child) self._refresh_btn = Gtk.Button(stock=Gtk.STOCK_REFRESH) action_area = self.toplevel.get_action_area() action_area.pack_start(self._refresh_btn, True, True, 6) action_area.set_child_secondary(self._refresh_btn, True) self._refresh_btn.connect('clicked', self._on_refresh_btn__clicked) self._refresh_btn.show() sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.vbox.add(sw) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.NONE) sw.add(viewport) alignment = Gtk.Alignment.new(0.0, 0.0, 1.0, 1.0) alignment.set_padding(6, 6, 6, 6) viewport.add(alignment) vbox = Gtk.VBox(spacing=12) alignment.add(vbox) resources = self._manager.resources.items() for i, (name, resource) in enumerate(resources): hbox = Gtk.HBox(spacing=6) img = Gtk.Image() hbox.pack_start(img, False, True, 0) lbl = Gtk.Label() hbox.pack_start(lbl, False, True, 0) buttonbox = Gtk.HButtonBox() hbox.pack_end(buttonbox, False, True, 0) self._widgets[name] = (img, lbl, buttonbox) vbox.pack_start(hbox, False, True, 6) if i < len(resources) - 1: separator = Gtk.HSeparator() vbox.pack_start(separator, False, True, 0) self.vbox.show_all() self._update_ui() def _update_ui(self): running_action = self._manager.running_action self._refresh_btn.set_sensitive(running_action is None) for name, resource in self._manager.resources.items(): img, lbl, buttonbox = self._widgets[name] status_stock, _ignored = _status_mapper[resource.status] img.set_from_stock(status_stock, Gtk.IconSize.LARGE_TOOLBAR) if resource.reason and resource.reason_long: text = "<b>%s</b>: %s\n<i>%s</i>" % ( api.escape(resource.label), api.escape(resource.reason), api.escape(resource.reason_long)) elif resource.reason: text = "<b>%s</b>: %s" % ( api.escape(resource.label), api.escape(resource.reason)) else: text = _("Status not available...") for child in buttonbox.get_children(): buttonbox.remove(child) for action in resource.get_actions(): btn = Gtk.Button.new_with_label(action.label) if running_action is not None: btn.set_sensitive(False) if action.admin_only and not self._is_admin: btn.set_sensitive(False) btn.set_tooltip_text( _("Only admins can execute this action")) # If the action is the running action, add a spinner together # with the label to indicate that it is running if action == running_action: spinner = Gtk.Spinner() hbox = Gtk.HBox(spacing=6) child = btn.get_child() btn.remove(child) hbox.add(child) hbox.add(spinner) btn.add(hbox) spinner.start() hbox.show_all() btn.show() btn.connect('clicked', self._on_action_btn__clicked, action) buttonbox.add(btn) lbl.set_markup(text) def _handle_action(self, action): retval = self._manager.handle_action(action) if action.threaded: thread = retval msg = _('Executing "%s". This might take a while...') % ( action.label, ) progress_dialog = ProgressDialog(msg, pulse=True) progress_dialog.start(wait=100) progress_dialog.set_transient_for(self.get_toplevel()) progress_dialog.set_title(action.resource.label) progress_dialog.connect( 'cancel', lambda d: terminate_thread(thread)) while thread.is_alive(): if Gtk.events_pending(): Gtk.main_iteration_do(False) progress_dialog.stop() self._update_ui() # # Callbacks # def _on_manager__status_changed(self, manager, status): self._update_ui() def _on_manager__action_finished(self, manager, action, retval): if isinstance(retval, Exception): warning(_('An error happened when executing "%s"') % (action.label, ), str(retval)) self._update_ui() def _on_action_btn__clicked(self, btn, action): self._handle_action(action) def _on_refresh_btn__clicked(self, btn): self._manager.refresh_and_notify()
def download_plugin(self, plugin_name, channel=None): """Download a plugin from webservice :param plugin_name: the name of the plugin to download :param channel: the channel the plugin belongs :returns: a deferred """ from stoqlib.lib.webservice import WebService default_store = get_default_store() existing_egg = default_store.find(PluginEgg, plugin_name=plugin_name).one() md5sum = existing_egg and existing_egg.egg_md5sum webapi = WebService() r = webapi.download_plugin(plugin_name, md5sum=md5sum, channel=channel) try: response = r.get_response() except Exception as e: return False, _("Failed to do the request: %s" % (e, )) code = response.status_code if code == 204: msg = _("No update needed. The plugin is already up to date.") log.info(msg) return True, msg if code != 200: return_messages = { 400: _("Plugin not available for this stoq version"), 401: _("The instance is not authorized to download the plugin"), 404: _("Plugin does not exist"), 405: _("This instance has not acquired the specified plugin"), } msg = return_messages.get(code, str(code)) log.warning(msg) return False, msg try: with io.BytesIO() as f: f.write(response.content) with ZipFile(f) as egg: if egg.testzip() is not None: raise BadZipfile md5sum = hashlib.md5(f.getvalue()).hexdigest() with new_store() as store: existing_egg = store.find(PluginEgg, plugin_name=plugin_name).one() if existing_egg is not None: existing_egg.egg_content = f.getvalue() existing_egg.egg_md5sum = md5sum else: PluginEgg( store=store, plugin_name=plugin_name, egg_md5sum=md5sum, egg_content=f.getvalue(), ) except BadZipfile: return False, _("The downloaded plugin is corrupted") self._reload() return True, _("Plugin download successful")
from stoqlib.gui.stockicons import (STOQ_FEEDBACK, STOQ_STATUS_NA, STOQ_STATUS_OK, STOQ_STATUS_WARNING, STOQ_STATUS_ERROR) from stoqlib.lib.message import warning from stoqlib.lib.translation import stoqlib_gettext as _ from stoqlib.lib.threadutils import terminate_thread from stoq.lib.status import ResourceStatus, ResourceStatusManager # FIXME: Improve those strings _status_mapper = { None: ( Gtk.STOCK_REFRESH, _("Checking status...")), ResourceStatus.STATUS_NA: ( STOQ_STATUS_NA, _("Status not available")), ResourceStatus.STATUS_OK: ( STOQ_STATUS_OK, _("Everything is running fine")), ResourceStatus.STATUS_WARNING: ( STOQ_STATUS_WARNING, _("Some services are in a warning state")), ResourceStatus.STATUS_ERROR: ( STOQ_STATUS_ERROR, _("Some services are in an error state")), }
def create_actions(self): group = get_accels('app.purchase') actions = [ # File ("OrderMenu", None, _("Order")), ("NewOrder", Gtk.STOCK_NEW, _("Order..."), group.get('new_order'), _("Create a new purchase order")), ("NewQuote", Gtk.STOCK_INDEX, _("Quote..."), group.get('new_quote'), _("Create a new purchase quote")), ("NewConsignment", None, _("Consignment..."), group.get('new_consignment'), _("Create a new purchase consignment")), ("NewProduct", None, _("Product..."), group.get('new_product'), _("Create a new product")), ('Reconciliation', None, _('Purchase reconciliation...'), group.get('new_reconciliation')), ("CloseInConsignment", None, _("Close consigment...")), ("ExportFilizola", None, _("Export Filizola File...")), # Edit ("StockCost", None, _("_Stock cost...")), # Search ("Categories", None, _("Categories..."), group.get("search_categories")), ("Products", None, _("Products..."), group.get("search_products")), ("ProductUnits", None, _("Product units..."), group.get("search_product_units")), ("ProductManufacturers", None, _("Manufacturers..."), group.get("search_product_manufacturers")), ("Services", None, _("Services..."), group.get("search_services")), ("SearchStockItems", None, _("Stock items..."), group.get("search_stock_items")), ("SearchClosedStockItems", None, _("Closed stock items..."), group.get("search_closed_stock_items")), ("Suppliers", None, _("Suppliers..."), group.get("search_suppliers")), ("Transporter", None, _("Transporters..."), group.get("search_transporters")), ("SearchQuotes", None, _("Quotes..."), group.get("search_quotes")), ("SearchPurchasedItems", None, _("Purchased items..."), group.get("search_purchased_items")), ("ProductsSoldSearch", None, _("Sold products..."), group.get("search_products_sold")), ("ProductsPriceSearch", None, _("Prices..."), group.get("search_prices")), ("SearchInConsignmentItems", None, _("Search consigment items..."), group.get("search_consignment_items")), # Order ("Confirm", Gtk.STOCK_APPLY, _("Confirm..."), group.get('order_confirm'), _("Confirm the selected order(s), marking it as sent to the " "supplier")), ("Cancel", Gtk.STOCK_CANCEL, _("Cancel..."), group.get('order_cancel'), _("Cancel the selected order")), ("Edit", Gtk.STOCK_EDIT, _("Edit..."), group.get('order_edit'), _("Edit the selected order, allowing you to change it's details") ), ("Details", Gtk.STOCK_INFO, _("Details..."), group.get('order_details'), _("Show details of the selected order")), ("Finish", Gtk.STOCK_APPLY, _("Finish..."), group.get('order_finish'), _('Complete the selected partially received order')), ] self.purchase_ui = self.add_ui_actions(actions) self.set_help_section(_("Purchase help"), 'app-purchase')
def get_date_format(): # Translators: This is the default date format in excel # columns, see the xlwt python library for more information return _('YY-MMM-D')
def on_ExportFilizola__activate(self, action): dest = generate_filizola_file(self.store) info(_('File saved in %s') % dest)
class PurchaseApp(ShellApp): app_title = _('Purchase') gladefile = "purchase" search_spec = PurchaseOrderView search_label = _('matching:') report_table = PurchaseReport action_permissions = { 'ProductUnits': ('ProductUnit', PermissionManager.PERM_SEARCH), 'NewProduct': ('Product', PermissionManager.PERM_CREATE), 'Products': ('Product', PermissionManager.PERM_SEARCH), 'Services': ('Service', PermissionManager.PERM_SEARCH), } # # Application # def create_actions(self): group = get_accels('app.purchase') actions = [ # File ("OrderMenu", None, _("Order")), ("NewOrder", Gtk.STOCK_NEW, _("Order..."), group.get('new_order'), _("Create a new purchase order")), ("NewQuote", Gtk.STOCK_INDEX, _("Quote..."), group.get('new_quote'), _("Create a new purchase quote")), ("NewConsignment", None, _("Consignment..."), group.get('new_consignment'), _("Create a new purchase consignment")), ("NewProduct", None, _("Product..."), group.get('new_product'), _("Create a new product")), ('Reconciliation', None, _('Purchase reconciliation...'), group.get('new_reconciliation')), ("CloseInConsignment", None, _("Close consigment...")), ("ExportFilizola", None, _("Export Filizola File...")), # Edit ("StockCost", None, _("_Stock cost...")), # Search ("Categories", None, _("Categories..."), group.get("search_categories")), ("Products", None, _("Products..."), group.get("search_products")), ("ProductUnits", None, _("Product units..."), group.get("search_product_units")), ("ProductManufacturers", None, _("Manufacturers..."), group.get("search_product_manufacturers")), ("Services", None, _("Services..."), group.get("search_services")), ("SearchStockItems", None, _("Stock items..."), group.get("search_stock_items")), ("SearchClosedStockItems", None, _("Closed stock items..."), group.get("search_closed_stock_items")), ("Suppliers", None, _("Suppliers..."), group.get("search_suppliers")), ("Transporter", None, _("Transporters..."), group.get("search_transporters")), ("SearchQuotes", None, _("Quotes..."), group.get("search_quotes")), ("SearchPurchasedItems", None, _("Purchased items..."), group.get("search_purchased_items")), ("ProductsSoldSearch", None, _("Sold products..."), group.get("search_products_sold")), ("ProductsPriceSearch", None, _("Prices..."), group.get("search_prices")), ("SearchInConsignmentItems", None, _("Search consigment items..."), group.get("search_consignment_items")), # Order ("Confirm", Gtk.STOCK_APPLY, _("Confirm..."), group.get('order_confirm'), _("Confirm the selected order(s), marking it as sent to the " "supplier")), ("Cancel", Gtk.STOCK_CANCEL, _("Cancel..."), group.get('order_cancel'), _("Cancel the selected order")), ("Edit", Gtk.STOCK_EDIT, _("Edit..."), group.get('order_edit'), _("Edit the selected order, allowing you to change it's details") ), ("Details", Gtk.STOCK_INFO, _("Details..."), group.get('order_details'), _("Show details of the selected order")), ("Finish", Gtk.STOCK_APPLY, _("Finish..."), group.get('order_finish'), _('Complete the selected partially received order')), ] self.purchase_ui = self.add_ui_actions(actions) self.set_help_section(_("Purchase help"), 'app-purchase') def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self.window.add_print_items() self.window.add_export_items([self.ExportFilizola]) self.window.add_extra_items([self.CloseInConsignment, self.StockCost]) self.window.add_new_items([ self.NewOrder, self.NewQuote, self.NewProduct, self.NewConsignment, self.Reconciliation ]) self.window.add_search_items([ self.Products, self.Services, self.Categories, self.ProductUnits, self.ProductManufacturers, self.SearchStockItems, self.SearchClosedStockItems, self.Transporter, self.Suppliers, self.SearchQuotes, self.SearchPurchasedItems, self.ProductsSoldSearch, self.ProductsPriceSearch, self.SearchInConsignmentItems, ]) self.search.set_summary_label(column='total', label=('<b>%s</b>' % api.escape(_('Orders total:'))), format='<b>%s</b>', parent=self.get_statusbar_message_area()) self.results.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.set_sensitive([self.Confirm], False) self._inventory_widgets = [ self.NewConsignment, self.CloseInConsignment ] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def activate(self, refresh=True): if refresh: self.refresh() self._update_view() self.results.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.check_open_inventory() self.search.focus_search_entry() def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'purchase.Details', True), ('fa-edit-symbolic', _('Edit'), 'purchase.Edit', True), ('fa-check-symbolic', _('Confirm'), 'purchase.Confirm', True), ('fa-ban-symbolic', _('Cancel'), 'purchase.Cancel', True), ('', _('Finish'), 'purchase.Finish', False), ] return options def search_completed(self, results, states): if len(results): return supplier, status = states[:2] if len(states) > 2 or (supplier.text == '' and status.value is None): self.search.set_message( "%s\n\n%s" % (_("No orders could be found."), _("Would you like to %s ?") % ('<a href="new_order">%s</a>' % (api.escape(_("create a new order"), ))))) # FIXME: Push number of results to Statusbar def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.set_text_field_columns( ['supplier_name', 'identifier_str', 'invoice_numbers']) self.status_filter = ComboSearchFilter(_('Show orders'), self._get_status_values()) self.add_filter(self.status_filter, SearchFilterPosition.TOP, ['status']) self.branch_filter = self.create_branch_filter( column=PurchaseOrderView.branch_id) def get_columns(self): return [ IdentifierColumn('identifier', title=_('Purchase #')), SearchColumn('status_str', title=_('Status'), width=100, data_type=str, search_attribute='status', valid_values=self._get_status_values(), visible=False), SearchColumn('invoice_numbers', title=_('Invoice'), width=100, data_type=str, visible=False), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), width=90, data_type=datetime.date, sorted=True, order=Gtk.SortType.DESCENDING), SearchColumn('receival_date', title=_('Receival'), long_title=_('Received date'), width=90, data_type=datetime.date, order=Gtk.SortType.DESCENDING), SearchColumn('supplier_name', title=_('Supplier'), data_type=str, searchable=True, expand=True, ellipsize=Pango.EllipsizeMode.END), SearchColumn('ordered_quantity', title=_('Ordered'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('received_quantity', title=_('Received'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('total', title=_('Total'), data_type=currency, width=120) ] def print_report(self, *args, **kwargs): # PurchaseReport needs a status arg kwargs['status'] = self.status_filter.get_state().value super(PurchaseApp, self).print_report(*args, **kwargs) def search_for_date(self, date): dfilter = DateSearchFilter(_("Expected receival date")) dfilter.set_removable() dfilter.select(data=DateSearchFilter.Type.USER_DAY) self.add_filter(dfilter, columns=["expected_receival_date"]) dfilter.start_date.set_date(date) self.refresh() # # Private # def _update_totals(self): self._update_view() def _update_list_aware_widgets(self, has_items): self.set_sensitive([self.Edit, self.Details], has_items) def _update_view(self): self._update_list_aware_widgets(len(self.results)) selection = self.results.get_selected_rows() can_edit = one_selected = len(selection) == 1 can_finish = False if selection: can_send_supplier = all(order.status == PurchaseOrder.ORDER_PENDING for order in selection) can_cancel = all(order_view.purchase.can_cancel() for order_view in selection) else: can_send_supplier = False can_cancel = False if one_selected: can_edit = (selection[0].status == PurchaseOrder.ORDER_PENDING or selection[0].status == PurchaseOrder.ORDER_QUOTING or selection[0].status == PurchaseOrder.ORDER_CONFIRMED) can_finish = (selection[0].status == PurchaseOrder.ORDER_CONFIRMED and selection[0].received_quantity > 0) self.set_sensitive([self.Cancel], can_cancel) self.set_sensitive([self.Edit], can_edit) self.set_sensitive([self.Confirm], can_send_supplier) self.set_sensitive([self.Details], one_selected) self.set_sensitive([self.Finish], can_finish) def _new_order(self, order=None, edit_mode=False): with api.new_store() as store: order = store.fetch(order) self.run_dialog(PurchaseWizard, store, order, edit_mode) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) def _edit_order(self): selected = self.results.get_selected_rows() qty = len(selected) if qty != 1: raise ValueError('You should have only one order selected, ' 'got %d instead' % qty) purchase = selected[0].purchase if purchase.status == PurchaseOrder.ORDER_QUOTING: self._quote_order(purchase) else: self._new_order(purchase, edit_mode=False) def _run_details_dialog(self): order_views = self.results.get_selected_rows() qty = len(order_views) if qty != 1: raise ValueError('You should have only one order selected ' 'at this point, got %d' % qty) self.run_dialog(PurchaseDetailsDialog, self.store, model=order_views[0].purchase) def _send_selected_items_to_supplier(self): orders = self.results.get_selected_rows() valid_order_views = [ order for order in orders if order.status == PurchaseOrder.ORDER_PENDING ] if not valid_order_views: warning(_("There are no pending orders selected.")) return msg = stoqlib_ngettext( _("The selected order will be marked as sent."), _("The %d selected orders will be marked as sent.") % len(valid_order_views), len(valid_order_views)) confirm_label = stoqlib_ngettext(_("Confirm order"), _("Confirm orders"), len(valid_order_views)) if not yesno(msg, Gtk.ResponseType.YES, confirm_label, _("Don't confirm")): return with api.new_store() as store: for order_view in valid_order_views: order = store.fetch(order_view.purchase) order.confirm() self.refresh() self.select_result(orders) def _finish_order(self): order_views = self.results.get_selected_rows() qty = len(order_views) if qty != 1: raise ValueError('You should have only one order selected ' 'at this point, got %d' % qty) with api.new_store() as store: order = store.fetch(order_views[0].purchase) self.run_dialog(PurchaseFinishWizard, store, order) self.refresh() self.select_result(order_views) def _cancel_order(self): order_views = self.results.get_selected_rows() assert all(ov.purchase.can_cancel() for ov in order_views) cancel_label = stoqlib_ngettext(_("Cancel order"), _("Cancel orders"), len(order_views)) select_label = stoqlib_ngettext( _('The selected order will be cancelled.'), _('The selected orders will be cancelled.'), len(order_views)) if not yesno(select_label, Gtk.ResponseType.YES, cancel_label, _("Don't cancel")): return with api.new_store() as store: for order_view in order_views: order = store.fetch(order_view.purchase) order.cancel() self._update_totals() self.refresh() self.select_result(order_views) def _get_status_values(self): items = [(text, value) for value, text in PurchaseOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items def _quote_order(self, quote=None): with api.new_store() as store: quote = store.fetch(quote) self.run_dialog(QuotePurchaseWizard, store, quote) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) def _new_product(self): ProductCreateWizard.run_wizard(self) def _new_consignment(self): with api.new_store() as store: self.run_dialog(ConsignmentWizard, store, model=None) if store.committed: self.refresh() res = self.store.find(PurchaseOrderView, id=store.retval.id).one() self.select_result(res) # # Kiwi Callbacks # def on_results__row_activated(self, klist, purchase_order_view): self._run_details_dialog() def on_results__selection_changed(self, results, selected): self._update_view() def on_results__activate_link(self, results, uri): if uri == 'new_order': self._new_order() def _on_results__double_click(self, results, order): self._run_details_dialog() def _on_results__has_rows(self, results, has_items): self._update_list_aware_widgets(has_items) def on_Details__activate(self, action): self._run_details_dialog() def on_Edit__activate(self, action): self._edit_order() def on_Cancel__activate(self, action): self._cancel_order() def on_Confirm__activate(self, button): self._send_selected_items_to_supplier() def on_Finish__activate(self, action): self._finish_order() def on_ExportFilizola__activate(self, action): dest = generate_filizola_file(self.store) info(_('File saved in %s') % dest) # Order def on_StockCost__activate(self, action): self.run_dialog(StockCostDialog, self.store, None) # Consignment def on_CloseInConsignment__activate(self, action): with api.new_store() as store: self.run_dialog(CloseInConsignmentWizard, store) def on_SearchInConsignmentItems__activate(self, action): self.run_dialog(ConsignmentItemSearch, self.store) def on_Categories__activate(self, action): self.run_dialog(SellableCategorySearch, self.store) def on_SearchQuotes__activate(self, action): with api.new_store() as store: self.run_dialog(ReceiveQuoteWizard, store) self.refresh() def on_SearchPurchasedItems__activate(self, action): self.run_dialog(PurchasedItemsSearch, self.store) def on_SearchStockItems__activate(self, action): self.run_dialog(ProductStockSearch, self.store) def on_SearchClosedStockItems__activate(self, action): self.run_dialog(ProductClosedStockSearch, self.store) def on_Suppliers__activate(self, action): self.run_dialog(SupplierSearch, self.store, hide_footer=True) def on_Products__activate(self, action): self.run_dialog(ProductSearch, self.store) def on_ProductUnits__activate(self, action): self.run_dialog(SellableUnitSearch, self.store) def on_ProductManufacturers__activate(self, action): self.run_dialog(ProductManufacturerDialog, self.store) def on_Services__activate(self, action): self.run_dialog(ServiceSearch, self.store, hide_price_column=True) def on_Transporter__activate(self, action): self.run_dialog(TransporterSearch, self.store, hide_footer=True) def on_ProductsSoldSearch__activate(self, action): self.run_dialog(ProductsSoldSearch, self.store) def on_ProductsPriceSearch__activate(self, action): with api.new_store() as store: self.run_dialog(SellableMassEditorDialog, store) # Toolitem def on_NewOrder__activate(self, action): self._new_order() def on_NewQuote__activate(self, action): self._quote_order() def on_NewProduct__activate(self, action): self._new_product() def on_NewConsignment__activate(self, action): self._new_consignment() def on_Reconciliation__activate(self, button): with api.new_store() as store: self.run_dialog(PurchaseReconciliationWizard, store)
def get_columns(self): return [ IdentifierColumn('identifier', title=_('Purchase #')), SearchColumn('status_str', title=_('Status'), width=100, data_type=str, search_attribute='status', valid_values=self._get_status_values(), visible=False), SearchColumn('invoice_numbers', title=_('Invoice'), width=100, data_type=str, visible=False), SearchColumn('open_date', title=_('Opened'), long_title=_('Date Opened'), width=90, data_type=datetime.date, sorted=True, order=Gtk.SortType.DESCENDING), SearchColumn('receival_date', title=_('Receival'), long_title=_('Received date'), width=90, data_type=datetime.date, order=Gtk.SortType.DESCENDING), SearchColumn('supplier_name', title=_('Supplier'), data_type=str, searchable=True, expand=True, ellipsize=Pango.EllipsizeMode.END), SearchColumn('ordered_quantity', title=_('Ordered'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('received_quantity', title=_('Received'), data_type=Decimal, width=90, format_func=format_quantity), SearchColumn('total', title=_('Total'), data_type=currency, width=120) ]
def _get_status_values(self): items = [(text, value) for value, text in PurchaseOrder.statuses.items()] items.insert(0, (_('Any'), None)) return items
def get_columns(self): self._status_col = SearchColumn('status_name', title=_('Status'), data_type=str, width=80, visible=True, search_attribute='status', valid_values=self._get_status_values()) cols = [IdentifierColumn('identifier', title=_('Sale #'), sorted=True), SearchColumn('coupon_id', title=_('Coupon #'), width=100, data_type=int, visible=False), SearchColumn('paid', title=_('Paid'), width=120, data_type=bool, visible=False), SearchColumn('open_date', title=_('Open date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('close_date', title=_('Close date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('confirm_date', title=_('Confirm date'), data_type=date, justify=Gtk.Justification.RIGHT, visible=False, width=120), SearchColumn('cancel_date', title=_('Cancel date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('return_date', title=_('Return date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('expire_date', title=_('Expire date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), self._status_col] if api.sysparam.get_bool('USE_SALE_TOKEN'): cols.append(Column('token_str', title=_(u'Sale token'), data_type=str, visible=True)) cols.extend([ SearchColumn('client_name', title=_('Client'), data_type=str, width=140, expand=True, ellipsize=Pango.EllipsizeMode.END), SearchColumn('client_fancy_name', title=_('Client fancy name'), data_type=str, width=140, expand=True, visible=False, ellipsize=Pango.EllipsizeMode.END), SearchColumn('salesperson_name', title=_('Salesperson'), data_type=str, width=130, ellipsize=Pango.EllipsizeMode.END), SearchColumn('total_quantity', title=_('Items'), data_type=decimal.Decimal, width=60, format_func=format_quantity), SearchColumn('total', title=_('Gross total'), data_type=currency, width=120, search_attribute='_total'), SearchColumn('net_total', title=_('Net total'), data_type=currency, width=120, search_attribute='_net_total')]) return cols
def _get_status_values(self): items = [(value, key) for key, value in Sale.statuses.items()] items.insert(0, (_('Any'), None)) return items
class SalesApp(ShellApp): app_title = _('Sales') gladefile = 'sales_app' search_spec = SaleView search_label = _('matching:') report_table = SalesReport cols_info = {Sale.STATUS_INITIAL: 'open_date', Sale.STATUS_CONFIRMED: 'confirm_date', Sale.STATUS_ORDERED: 'open_date', Sale.STATUS_CANCELLED: 'cancel_date', Sale.STATUS_QUOTE: 'open_date', Sale.STATUS_RETURNED: 'return_date', Sale.STATUS_RENEGOTIATED: 'close_date'} action_permissions = { "SalesPrintInvoice": ('app.sales.print_invoice', PermissionManager.PERM_SEARCH), } def __init__(self, window, store=None): self.summary_label = None self._visible_date_col = None self._extra_summary = None ShellApp.__init__(self, window, store=store) self.search.connect('search-completed', self._on_search_completed) # # Application # def create_actions(self): group = get_accels('app.sales') actions = [ # File ("SaleQuote", None, _("Sale quote..."), '', _('Create a new quote for a sale')), ("WorkOrderQuote", None, _("Sale with work order..."), '', _('Create a new quote for a sale with work orders')), ("LoanNew", None, _("Loan...")), ("LoanClose", None, _("Close loan...")), # Search ("SearchSoldItemsByBranch", None, _("Sold items by branch..."), group.get("search_sold_items_by_branch"), _("Search for sold items by branch")), ("SearchSalesByPaymentMethod", None, _("Sales by payment method..."), group.get("search_sales_by_payment")), ("SearchSalesPersonSales", None, _("Total sales made by salesperson..."), group.get("search_salesperson_sales"), _("Search for sales by payment method")), ("SearchProduct", None, _("Products..."), group.get("search_products"), _("Search for products")), ("SearchService", None, _("Services..."), group.get("search_services"), _("Search for services")), ("SearchDelivery", None, _("Deliveries..."), group.get("search_deliveries"), _("Search for deliveries")), ("SearchClient", None, _("Clients..."), group.get("search_clients"), _("Search for clients")), ("SearchClientCalls", None, _("Client Calls..."), group.get("search_client_calls"), _("Search for client calls")), ("SearchCreditCheckHistory", None, _("Client credit check history..."), group.get("search_credit_check_history"), _("Search for client check history")), ("SearchCommission", None, _("Commissions..."), group.get("search_commissions"), _("Search for salespersons commissions")), ("LoanSearch", None, _("Loans..."), group.get("search_loans")), ("LoanSearchItems", None, _("Loan items..."), group.get("search_loan_items")), ("ReturnedSaleSearch", None, _("Returned sales..."), group.get("returned_sales")), ("SearchUnconfirmedSaleItems", None, _("Unconfirmed sale items..."), group.get("search_reserved_product"), _("Search for unconfirmed sale items")), ("SearchClientsWithSale", None, _("Clients with sales..."), None, _("Search for regular clients")), ("SearchClientsWithCredit", None, _("Clients with credit..."), None, _("Search for clients that have credit")), ("SearchSoldItemsByClient", None, _("Sold items by client..."), None, _("Search for products sold by client")), # Sale ("SaleMenu", None, _("Sale")), ("SalesCancel", None, _("Cancel...")), ("ChangeClient", Gtk.STOCK_EDIT, _("Change client...")), ("ChangeSalesperson", Gtk.STOCK_EDIT, _("Change salesperson...")), ("SalesPrintInvoice", Gtk.STOCK_PRINT, _("_Print invoice...")), ("Return", Gtk.STOCK_CANCEL, _("Return..."), '', _("Return the selected sale, canceling it's payments")), ("Edit", Gtk.STOCK_EDIT, _("Edit..."), '', _("Edit the selected sale, allowing you to change the details " "of it")), ("Details", Gtk.STOCK_INFO, _("Details..."), '', _("Show details of the selected sale")) ] self.sales_ui = self.add_ui_actions(actions) self.set_help_section(_("Sales help"), 'app-sales') def get_domain_options(self): options = [ ('fa-info-circle-symbolic', _('Details'), 'sales.Details', True), ('fa-edit-symbolic', _('Edit'), 'sales.Edit', True), ('fa-undo-symbolic', _('Return'), 'sales.Return', True), ('fa-ban-symbolic', _('Cancel'), 'sales.SalesCancel', True), ] if api.sysparam.get_bool('CHANGE_CLIENT_AFTER_CONFIRMED'): options.append(('', _('Change client'), 'sales.ChangeClient', False)) if api.sysparam.get_bool('CHANGE_SALESPERSON_AFTER_CONFIRMED'): options.append(('', _('Change salesperson'), 'sales.ChangeSalespeson', False)) return options def create_ui(self): if api.sysparam.get_bool('SMART_LIST_LOADING'): self.search.enable_lazy_search() self._setup_columns() self._setup_widgets() self.window.add_print_items() self.window.add_export_items() self.window.add_extra_items([self.LoanClose]) self.window.add_new_items([self.SaleQuote, self.WorkOrderQuote, self.LoanNew]) self.window.add_search_items( [self.SearchProduct, self.SearchService, self.SearchDelivery]) self.window.add_search_items( [self.SearchClient, self.SearchClientCalls, self.SearchCreditCheckHistory, self.SearchClientsWithSale, self.SearchClientsWithCredit]) self.window.add_search_items( [self.SearchSalesByPaymentMethod, self.ReturnedSaleSearch, self.SearchCommission, self.SearchSalesPersonSales]) self.window.add_search_items( [self.SearchUnconfirmedSaleItems, self.SearchSoldItemsByBranch, self.SearchSoldItemsByClient]) self.window.add_search_items( [self.LoanSearch, self.LoanSearchItems]) def activate(self, refresh=True): if refresh: self.refresh() self.check_open_inventory() self._update_toolbar() self.search.focus_search_entry() def set_open_inventory(self): self.set_sensitive(self._inventory_widgets, False) def create_filters(self): self.set_text_field_columns(['client_name', 'salesperson_name', 'identifier_str', 'token_code', 'token_name']) status_filter = ComboSearchFilter(_('Show sales'), self._get_filter_options()) status_filter.combo.set_row_separator_func( lambda model, titer: model[titer][0] == 'sep') executer = self.search.get_query_executer() executer.add_filter_query_callback( status_filter, self._get_status_query) self.add_filter(status_filter, position=SearchFilterPosition.TOP) self.create_branch_filter(column=Sale.branch_id) def get_columns(self): self._status_col = SearchColumn('status_name', title=_('Status'), data_type=str, width=80, visible=True, search_attribute='status', valid_values=self._get_status_values()) cols = [IdentifierColumn('identifier', title=_('Sale #'), sorted=True), SearchColumn('coupon_id', title=_('Coupon #'), width=100, data_type=int, visible=False), SearchColumn('paid', title=_('Paid'), width=120, data_type=bool, visible=False), SearchColumn('open_date', title=_('Open date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('close_date', title=_('Close date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('confirm_date', title=_('Confirm date'), data_type=date, justify=Gtk.Justification.RIGHT, visible=False, width=120), SearchColumn('cancel_date', title=_('Cancel date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('return_date', title=_('Return date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), SearchColumn('expire_date', title=_('Expire date'), width=120, data_type=date, justify=Gtk.Justification.RIGHT, visible=False), self._status_col] if api.sysparam.get_bool('USE_SALE_TOKEN'): cols.append(Column('token_str', title=_(u'Sale token'), data_type=str, visible=True)) cols.extend([ SearchColumn('client_name', title=_('Client'), data_type=str, width=140, expand=True, ellipsize=Pango.EllipsizeMode.END), SearchColumn('client_fancy_name', title=_('Client fancy name'), data_type=str, width=140, expand=True, visible=False, ellipsize=Pango.EllipsizeMode.END), SearchColumn('salesperson_name', title=_('Salesperson'), data_type=str, width=130, ellipsize=Pango.EllipsizeMode.END), SearchColumn('total_quantity', title=_('Items'), data_type=decimal.Decimal, width=60, format_func=format_quantity), SearchColumn('total', title=_('Gross total'), data_type=currency, width=120, search_attribute='_total'), SearchColumn('net_total', title=_('Net total'), data_type=currency, width=120, search_attribute='_net_total')]) return cols # # Private # def _create_summary_labels(self): parent = self.get_statusbar_message_area() self.search.set_summary_label(column='net_total', label=('<b>%s</b>' % api.escape(_('Gross total:'))), format='<b>%s</b>', parent=parent) # Add an extra summary beyond the main one # XXX: Should we modify the summary api to make it support more than one # summary value? This is kind of ugly if self._extra_summary: parent.remove(self._extra_summary) self._extra_summary = LazySummaryLabel(klist=self.search.result_view, column='net_total', label=('<b>%s</b>' % api.escape(_('Net total:'))), value_format='<b>%s</b>') parent.pack_start(self._extra_summary, False, False, 0) self._extra_summary.show() def _setup_widgets(self): self._setup_slaves() self._inventory_widgets = [self.sale_toolbar.return_sale_button, self.Return, self.LoanNew, self.LoanClose] self.register_sensitive_group(self._inventory_widgets, lambda: not self.has_open_inventory()) def _setup_slaves(self): # This is only here to reuse the logic in it. self.sale_toolbar = SaleListToolbar(self.store, self.results, parent=self) def _update_toolbar(self, *args): sale_view = self.results.get_selected() # FIXME: Disable invoice printing if the sale was returned. Remove this # when we add proper support for returned sales invoice. can_print_invoice = bool(sale_view and sale_view.client_name is not None and sale_view.status != Sale.STATUS_RETURNED) self.set_sensitive([self.SalesPrintInvoice], can_print_invoice) self.set_sensitive([self.SalesCancel], bool(sale_view and sale_view.can_cancel())) self.set_sensitive([self.sale_toolbar.return_sale_button, self.Return], bool(sale_view and (sale_view.can_return() or sale_view.can_cancel()))) self.set_sensitive([self.sale_toolbar.return_sale_button, self.Details], bool(sale_view)) self.set_sensitive([self.sale_toolbar.edit_button, self.Edit], bool(sale_view and sale_view.can_edit())) # If the sale cannot be edit anymore, we only allow to change the client self.set_sensitive([self.ChangeClient], bool(sale_view and not sale_view.can_edit())) self.set_sensitive([self.ChangeSalesperson], bool(sale_view and not sale_view.can_edit())) self.sale_toolbar.set_report_filters(self.search.get_search_filters()) def _print_invoice(self): sale_view = self.results.get_selected() assert sale_view sale = sale_view.sale station = api.get_current_station(self.store) printer = InvoicePrinter.get_by_station(station, self.store) if printer is None: info(_("There are no invoice printer configured for this station")) return assert printer.layout invoice = SaleInvoice(sale, printer.layout) if not invoice.has_invoice_number() or sale.invoice.invoice_number: print_sale_invoice(invoice, printer) else: store = api.new_store() retval = self.run_dialog(SaleInvoicePrinterDialog, store, store.fetch(sale), printer) store.confirm(retval) store.close() def _setup_columns(self, sale_status=Sale.STATUS_CONFIRMED): self._status_col.visible = False if sale_status is None: # When there is no filter for sale status, show the # 'date started' column by default sale_status = Sale.STATUS_INITIAL self._status_col.visible = True if self._visible_date_col: self._visible_date_col.visible = False col = self.search.get_column_by_attribute(self.cols_info[sale_status]) if col is not None: self._visible_date_col = col col.visible = True self.results.set_columns(self.search.columns) # Adding summary label again and make it properly aligned with the # new columns setup self._create_summary_labels() def _get_status_values(self): items = [(value, key) for key, value in Sale.statuses.items()] items.insert(0, (_('Any'), None)) return items def _get_filter_options(self): options = [ (_('All Sales'), None), (_('Confirmed today'), FilterItem('custom', 'sold-today')), (_('Confirmed in the last 7 days'), FilterItem('custom', 'sold-7days')), (_('Confirmed in the last 28 days'), FilterItem('custom', 'sold-28days')), (_('Expired quotes'), FilterItem('custom', 'expired-quotes')), ('sep', None), ] for key, value in Sale.statuses.items(): options.append((value, FilterItem('status', key))) return options def _get_status_query(self, state): if state.value is None: # FIXME; We cannot return None here, otherwise, the query will have # a 'AND NULL', which will return nothing. return True if state.value.name == 'custom': self._setup_columns(None) return SALES_FILTERS[state.value.value] elif state.value.name == 'status': self._setup_columns(state.value.value) return SaleView.status == state.value.value raise AssertionError(state.value.name, state.value.value) def _new_sale_quote(self, wizard): if self.check_open_inventory(): warning(_("You cannot create a quote with an open inventory.")) return store = api.new_store() model = self.run_dialog(wizard, store) store.confirm(model) store.close() if model: self.refresh() def _search_product(self): hide_cost_column = not api.sysparam.get_bool('SHOW_COST_COLUMN_IN_SALES') self.run_dialog(ProductSearch, self.store, hide_footer=True, hide_toolbar=True, hide_cost_column=hide_cost_column) def _change_sale_client(self): sale_view = self.results.get_selected() with api.new_store() as store: sale = store.fetch(sale_view.sale) self.run_dialog(SaleClientEditor, store=store, model=sale) if store.committed: self.refresh() def _change_salesperson(self): sale_view = self.results.get_selected() with api.new_store() as store: sale = store.fetch(sale_view.sale) self.run_dialog(SalesPersonEditor, store=store, model=sale) if store.committed: self.refresh() # # Kiwi callbacks # def _on_sale_toolbar__sale_returned(self, toolbar, sale): self.refresh() def _on_sale_toolbar__sale_edited(self, toolbar, sale): self.refresh() def on_results__selection_changed(self, results, sale): self._update_toolbar() def on_results__has_rows(self, results, has_rows): self._update_toolbar() # Sales def on_SaleQuote__activate(self, action): self._new_sale_quote(wizard=SaleQuoteWizard) def on_WorkOrderQuote__activate(self, action): self._new_sale_quote(wizard=WorkOrderQuoteWizard) def on_SalesCancel__activate(self, action): sale_view = self.results.get_selected() # A plugin (e.g. ECF) can avoid the cancelation of a sale # because it wants it to be cancelled using another way if SaleAvoidCancelEvent.emit(sale_view.sale, Sale.STATUS_CANCELLED): return store = api.new_store() sale = store.fetch(sale_view.sale) msg_text = _(u"This will cancel the sale, Are you sure?") # nfce plugin cancellation event requires a minimum length for the # cancellation reason note. We can't set this in the plugin because it's # not possible to identify unically this NoteEditor. if get_plugin_manager().is_active('nfce'): note_min_length = 15 else: note_min_length = 0 retval = self.run_dialog( NoteEditor, store, model=None, message_text=msg_text, label_text=_(u"Reason"), mandatory=True, ok_button_label=_(u"Cancel sale"), cancel_button_label=_(u"Don't cancel"), min_length=note_min_length) if not retval: store.rollback() return # Try to cancel the sale with sefaz. Don't cancel the sale if sefaz # reject it. if StockOperationTryFiscalCancelEvent.emit(sale, retval.notes) is False: warning(_("The cancellation was not authorized by SEFAZ. You should " "do a sale return.")) return sale.cancel(retval.notes) store.commit(close=True) self.refresh() def on_ChangeClient__activate(self, action): self._change_sale_client() def on_ChangeSalesperson__activate(self, action): self._change_salesperson() def on_SalesPrintInvoice__activate(self, action): return self._print_invoice() # Loan def on_LoanNew__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(NewLoanWizard, store) store.confirm(model) store.close() def on_LoanClose__activate(self, action): if self.check_open_inventory(): return store = api.new_store() model = self.run_dialog(CloseLoanWizard, store) store.confirm(model) store.close() def on_LoanSearch__activate(self, action): self.run_dialog(LoanSearch, self.store) def on_LoanSearchItems__activate(self, action): self.run_dialog(LoanItemSearch, self.store) def on_ReturnedSaleSearch__activate(self, action): self.run_dialog(ReturnedSaleSearch, self.store) def on_SearchUnconfirmedSaleItems__activate(self, action): self.run_dialog(UnconfirmedSaleItemsSearch, self.store) # Search def on_SearchClient__activate(self, button): self.run_dialog(ClientSearch, self.store, hide_footer=True) def on_SearchProduct__activate(self, button): self._search_product() def on_SearchCommission__activate(self, button): self.run_dialog(CommissionSearch, self.store) def on_SearchClientCalls__activate(self, action): self.run_dialog(ClientCallsSearch, self.store) def on_SearchCreditCheckHistory__activate(self, action): self.run_dialog(CreditCheckHistorySearch, self.store) def on_SearchService__activate(self, button): self.run_dialog(ServiceSearch, self.store, hide_toolbar=True) def on_SearchSoldItemsByBranch__activate(self, button): self.run_dialog(SoldItemsByBranchSearch, self.store) def on_SearchSalesByPaymentMethod__activate(self, button): self.run_dialog(SalesByPaymentMethodSearch, self.store) def on_SearchDelivery__activate(self, action): self.run_dialog(DeliverySearch, self.store) def on_SearchSalesPersonSales__activate(self, action): self.run_dialog(SalesPersonSalesSearch, self.store) def on_SearchClientsWithSale__activate(self, action): self.run_dialog(ClientsWithSaleSearch, self.store) def on_SearchClientsWithCredit__activate(self, action): self.run_dialog(ClientsWithCreditSearch, self.store) def on_SearchSoldItemsByClient__activate(self, action): self.run_dialog(SoldItemsByClientSearch, self.store) # Toolbar def on_Edit__activate(self, action): self.sale_toolbar.edit() def on_Details__activate(self, action): self.sale_toolbar.show_details() def on_Return__activate(self, action): if self.check_open_inventory(): return self.sale_toolbar.return_sale() # Sale toobar def on_sale_toolbar__sale_edited(self, widget, sale): self.refresh() def on_sale_toolbar__sale_returned(self, widget, sale): self.refresh() # Search def _on_search_completed(self, *args): if not (self.search.result_view.lazy_search_enabled() and len(self.search.result_view)): return post = self.search.result_view.get_model().get_post_data() if post is not None: self._extra_summary.update_total(post.net_sum)
def create_actions(self): group = get_accels('app.sales') actions = [ # File ("SaleQuote", None, _("Sale quote..."), '', _('Create a new quote for a sale')), ("WorkOrderQuote", None, _("Sale with work order..."), '', _('Create a new quote for a sale with work orders')), ("LoanNew", None, _("Loan...")), ("LoanClose", None, _("Close loan...")), # Search ("SearchSoldItemsByBranch", None, _("Sold items by branch..."), group.get("search_sold_items_by_branch"), _("Search for sold items by branch")), ("SearchSalesByPaymentMethod", None, _("Sales by payment method..."), group.get("search_sales_by_payment")), ("SearchSalesPersonSales", None, _("Total sales made by salesperson..."), group.get("search_salesperson_sales"), _("Search for sales by payment method")), ("SearchProduct", None, _("Products..."), group.get("search_products"), _("Search for products")), ("SearchService", None, _("Services..."), group.get("search_services"), _("Search for services")), ("SearchDelivery", None, _("Deliveries..."), group.get("search_deliveries"), _("Search for deliveries")), ("SearchClient", None, _("Clients..."), group.get("search_clients"), _("Search for clients")), ("SearchClientCalls", None, _("Client Calls..."), group.get("search_client_calls"), _("Search for client calls")), ("SearchCreditCheckHistory", None, _("Client credit check history..."), group.get("search_credit_check_history"), _("Search for client check history")), ("SearchCommission", None, _("Commissions..."), group.get("search_commissions"), _("Search for salespersons commissions")), ("LoanSearch", None, _("Loans..."), group.get("search_loans")), ("LoanSearchItems", None, _("Loan items..."), group.get("search_loan_items")), ("ReturnedSaleSearch", None, _("Returned sales..."), group.get("returned_sales")), ("SearchUnconfirmedSaleItems", None, _("Unconfirmed sale items..."), group.get("search_reserved_product"), _("Search for unconfirmed sale items")), ("SearchClientsWithSale", None, _("Clients with sales..."), None, _("Search for regular clients")), ("SearchClientsWithCredit", None, _("Clients with credit..."), None, _("Search for clients that have credit")), ("SearchSoldItemsByClient", None, _("Sold items by client..."), None, _("Search for products sold by client")), # Sale ("SaleMenu", None, _("Sale")), ("SalesCancel", None, _("Cancel...")), ("ChangeClient", Gtk.STOCK_EDIT, _("Change client...")), ("ChangeSalesperson", Gtk.STOCK_EDIT, _("Change salesperson...")), ("SalesPrintInvoice", Gtk.STOCK_PRINT, _("_Print invoice...")), ("Return", Gtk.STOCK_CANCEL, _("Return..."), '', _("Return the selected sale, canceling it's payments")), ("Edit", Gtk.STOCK_EDIT, _("Edit..."), '', _("Edit the selected sale, allowing you to change the details " "of it")), ("Details", Gtk.STOCK_INFO, _("Details..."), '', _("Show details of the selected sale")) ] self.sales_ui = self.add_ui_actions(actions) self.set_help_section(_("Sales help"), 'app-sales')
class CalendarApp(ShellApp): app_title = _('Calendar') gladefile = 'calendar' def __init__(self, window, store=None): # Create this here because CalendarView will update it. # It will only be shown on create_ui though self.date_label = gtk.Label('') self._calendar = CalendarView(self) ShellApp.__init__(self, window, store=store) threadit(self._setup_daemon) def _setup_daemon(self): daemon = start_daemon() assert daemon.running self._calendar.set_daemon_uri(daemon.server_uri) schedule_in_main_thread(self._calendar.load) # # ShellApp overrides # def create_actions(self): group = get_accels('app.calendar') actions = [ # File ('NewClientCall', None, _("Client call"), group.get('new_client_call'), _("Add a new client call")), ('NewPayable', None, _("Account payable"), group.get('new_payable'), _("Add a new account payable")), ('NewReceivable', None, _("Account receivable"), group.get('new_receivable'), _("Add a new account receivable")), ('NewWorkOrder', None, _("Work order"), group.get('new_work_order'), _("Add a new work order")), # View ('Back', gtk.STOCK_GO_BACK, _("Back"), group.get('go_back'), _("Go back")), ('Forward', gtk.STOCK_GO_FORWARD, _("Forward"), group.get('go_forward'), _("Go forward")), ('Today', STOQ_CALENDAR_TODAY, _("Show today"), group.get('show_today'), _("Show today")), ('CalendarEvents', None, _("Calendar events")), ('CurrentView', None, _("Display view as")), ] self.calendar_ui = self.add_ui_actions('', actions, filename='calendar.xml') self.set_help_section(_("Calendar help"), 'app-calendar') toggle_actions = [ ('AccountsPayableEvents', None, _("Accounts payable"), None, _("Show accounts payable in the list")), ('AccountsReceivableEvents', None, _("Accounts receivable"), None, _("Show accounts receivable in the list")), ('PurchaseEvents', None, _("Purchases"), None, _("Show purchases in the list")), ('ClientCallEvents', None, _("Client Calls"), None, _("Show client calls in the list")), ('ClientBirthdaysEvents', None, _("Client Birthdays"), None, _("Show client birthdays in the list")), ('WorkOrderEvents', None, _("Work orders"), None, _("Show work orders in the list")), ] self.add_ui_actions('', toggle_actions, 'ToggleActions', 'toggle') events_info = dict( in_payments=(self.AccountsReceivableEvents, self.NewReceivable, u'receivable'), out_payments=(self.AccountsPayableEvents, self.NewPayable, u'payable'), purchase_orders=(self.PurchaseEvents, None, u'stock'), client_calls=(self.ClientCallEvents, self.NewClientCall, u'sales'), client_birthdays=(self.ClientBirthdaysEvents, None, u'sales'), work_orders=(self.WorkOrderEvents, self.NewWorkOrder, u'services'), ) user = api.get_current_user(self.store) events = self._calendar.get_events() for event_name, value in events_info.items(): view_action, new_action, app = value view_action.props.active = events[event_name] # Disable feature if user does not have acces to required # application if not user.profile.check_app_permission(app): view_action.props.active = False view_action.set_sensitive(False) if new_action: new_action.set_sensitive(False) view_action.connect('notify::active', self._update_events) self._update_events() radio_actions = [ ('ViewMonth', STOQ_CALENDAR_MONTH, _("View as month"), '', _("Show one month")), ('ViewWeek', STOQ_CALENDAR_WEEK, _("View as week"), '', _("Show one week")), ('ViewDay', STOQ_CALENDAR_LIST, _("View as day"), '', _("Show one day")), ] self.add_ui_actions('', radio_actions, 'RadioActions', 'radio') self.ViewMonth.set_short_label(_("Month")) self.ViewWeek.set_short_label(_("Week")) self.ViewDay.set_short_label(_("Day")) self.ViewMonth.props.is_important = True self.ViewWeek.props.is_important = True self.ViewDay.props.is_important = True view = api.user_settings.get('calendar-view', 'month') if view == 'month': self.ViewMonth.props.active = True elif view == 'basicWeek': self.ViewWeek.props.active = True else: self.ViewDay.props.active = True def create_ui(self): self.window.add_new_items( [self.NewClientCall, self.NewPayable, self.NewReceivable]) # Reparent the toolbar, to show the date next to it. self.hbox = gtk.HBox() toolbar = self.uimanager.get_widget('/toolbar') toolbar.reparent(self.hbox) # A label to show the current calendar date. self.date_label.show() self.hbox.pack_start(self.date_label, False, False, 6) self.hbox.show() self.main_vbox.pack_start(self.hbox, False, False) self.main_vbox.pack_start(self._calendar) self._calendar.show() self.window.Print.set_tooltip(_("Print this calendar")) def activate(self, refresh=True): self.window.SearchToolItem.set_sensitive(False) # FIXME: Are we 100% sure we can always print something? # self.window.Print.set_sensitive(True) def deactivate(self): # Put the toolbar back at where it was main_vbox = self.window.main_vbox toolbar = self.uimanager.get_widget('/toolbar') self.hbox.remove(toolbar) main_vbox.pack_start(toolbar, False, False) main_vbox.reorder_child(toolbar, 1) self.uimanager.remove_ui(self.calendar_ui) self.window.SearchToolItem.set_sensitive(True) # Private def _update_events(self, *args): self._calendar.update_events( out_payments=self.AccountsPayableEvents.get_active(), in_payments=self.AccountsReceivableEvents.get_active(), purchase_orders=self.PurchaseEvents.get_active(), client_calls=self.ClientCallEvents.get_active(), client_birthdays=self.ClientBirthdaysEvents.get_active(), work_orders=self.WorkOrderEvents.get_active(), ) def _new_client_call(self): with api.new_store() as store: self.run_dialog(CallsEditor, store, None, None, Client) if store.committed: self._update_events() def _new_work_order(self): with api.new_store() as store: self.run_dialog(WorkOrderEditor, store) if store.committed: self._update_events() def _new_payment(self, editor): with api.new_store() as store: self.run_dialog(editor, store) if store.committed: self._update_events() # # Kiwi callbacks # # Toolbar def new_activate(self): if not self.NewClientCall.get_sensitive(): return self._new_client_call() def print_activate(self): self._calendar.print_() def export_spreadsheet_activate(self): pass def on_NewClientCall__activate(self, action): self._new_client_call() def on_NewPayable__activate(self, action): self._new_payment(OutPaymentEditor) def on_NewReceivable__activate(self, action): self._new_payment(InPaymentEditor) def on_NewWorkOrder__activate(self, action): self._new_work_order() def on_Back__activate(self, action): self._calendar.go_prev() def on_Today__activate(self, action): self._calendar.show_today() def on_Forward__activate(self, action): self._calendar.go_next() def on_ViewMonth__activate(self, action): self._calendar.change_view('month') def on_ViewWeek__activate(self, action): self._calendar.change_view('basicWeek') def on_ViewDay__activate(self, action): self._calendar.change_view('basicDay')
class ResourceStatus(GObject.GObject): """The status of a given resource""" gsignal('status-changed', int, str) (STATUS_NA, STATUS_OK, STATUS_WARNING, STATUS_ERROR) = range(4) status_label = { STATUS_NA: _("N/A"), STATUS_OK: _("OK"), STATUS_WARNING: _("WARNING"), STATUS_ERROR: _("ERROR"), } name = None label = None priority = 0 refresh_timeout = int(os.environ.get('STOQ_STATUS_REFRESH_TIMEOUT', 60)) def __init__(self): super(ResourceStatus, self).__init__() assert self.name is not None self.status = self.STATUS_NA self.reason = None self.reason_long = None GLib.timeout_add_seconds(self.refresh_timeout, self.refresh_and_notify) # Schedule first update for right after now. Dont call refresh_and_notify() # directly as it will block the interface briefly GLib.timeout_add_seconds(1, lambda: self.refresh_and_notify() and False) __hash__ = GObject.GObject.__hash__ def __eq__(self, other): if type(self) != type(other): return False return self.name == other.name @property def status_str(self): return self.status_label[self.status] def refresh(self): """Refresh the resource status Subclasses should override this and update :obj:`.status` and :obj:`.reason` Note that this will not be running on the main thread, so be cautelous with non thread-safe operations. """ raise NotImplementedError def get_actions(self): """Get the actions that can be run for this resource""" return [] def refresh_and_notify(self): """Refresh the resource status and notify for changes""" old_status, old_reason = self.status, self.reason self.refresh() if (self.status, self.reason) != (old_status, old_reason): self.emit('status-changed', self.status, self.reason)
def create_actions(self): group = get_accels('app.calendar') actions = [ # File ('NewClientCall', None, _("Client call"), group.get('new_client_call'), _("Add a new client call")), ('NewPayable', None, _("Account payable"), group.get('new_payable'), _("Add a new account payable")), ('NewReceivable', None, _("Account receivable"), group.get('new_receivable'), _("Add a new account receivable")), ('NewWorkOrder', None, _("Work order"), group.get('new_work_order'), _("Add a new work order")), # View ('Back', gtk.STOCK_GO_BACK, _("Back"), group.get('go_back'), _("Go back")), ('Forward', gtk.STOCK_GO_FORWARD, _("Forward"), group.get('go_forward'), _("Go forward")), ('Today', STOQ_CALENDAR_TODAY, _("Show today"), group.get('show_today'), _("Show today")), ('CalendarEvents', None, _("Calendar events")), ('CurrentView', None, _("Display view as")), ] self.calendar_ui = self.add_ui_actions('', actions, filename='calendar.xml') self.set_help_section(_("Calendar help"), 'app-calendar') toggle_actions = [ ('AccountsPayableEvents', None, _("Accounts payable"), None, _("Show accounts payable in the list")), ('AccountsReceivableEvents', None, _("Accounts receivable"), None, _("Show accounts receivable in the list")), ('PurchaseEvents', None, _("Purchases"), None, _("Show purchases in the list")), ('ClientCallEvents', None, _("Client Calls"), None, _("Show client calls in the list")), ('ClientBirthdaysEvents', None, _("Client Birthdays"), None, _("Show client birthdays in the list")), ('WorkOrderEvents', None, _("Work orders"), None, _("Show work orders in the list")), ] self.add_ui_actions('', toggle_actions, 'ToggleActions', 'toggle') events_info = dict( in_payments=(self.AccountsReceivableEvents, self.NewReceivable, u'receivable'), out_payments=(self.AccountsPayableEvents, self.NewPayable, u'payable'), purchase_orders=(self.PurchaseEvents, None, u'stock'), client_calls=(self.ClientCallEvents, self.NewClientCall, u'sales'), client_birthdays=(self.ClientBirthdaysEvents, None, u'sales'), work_orders=(self.WorkOrderEvents, self.NewWorkOrder, u'services'), ) user = api.get_current_user(self.store) events = self._calendar.get_events() for event_name, value in events_info.items(): view_action, new_action, app = value view_action.props.active = events[event_name] # Disable feature if user does not have acces to required # application if not user.profile.check_app_permission(app): view_action.props.active = False view_action.set_sensitive(False) if new_action: new_action.set_sensitive(False) view_action.connect('notify::active', self._update_events) self._update_events() radio_actions = [ ('ViewMonth', STOQ_CALENDAR_MONTH, _("View as month"), '', _("Show one month")), ('ViewWeek', STOQ_CALENDAR_WEEK, _("View as week"), '', _("Show one week")), ('ViewDay', STOQ_CALENDAR_LIST, _("View as day"), '', _("Show one day")), ] self.add_ui_actions('', radio_actions, 'RadioActions', 'radio') self.ViewMonth.set_short_label(_("Month")) self.ViewWeek.set_short_label(_("Week")) self.ViewDay.set_short_label(_("Day")) self.ViewMonth.props.is_important = True self.ViewWeek.props.is_important = True self.ViewDay.props.is_important = True view = api.user_settings.get('calendar-view', 'month') if view == 'month': self.ViewMonth.props.active = True elif view == 'basicWeek': self.ViewWeek.props.active = True else: self.ViewDay.props.active = True
def _startup(self): options = {} options['monthNames'] = dateutils.get_month_names() options['monthNamesShort'] = dateutils.get_short_month_names() options['dayNames'] = dateutils.get_day_names() options['dayNamesShort'] = dateutils.get_short_day_names() options['buttonText'] = { "today": _('today'), "month": _('month'), "week": _('week'), "day": _('day') } options['defaultView'] = api.user_settings.get('calendar-view', 'month') # FIXME: This should not be tied to the language, rather be # picked up from libc, but it's a bit of work to translate # one into another so just take a shortcut options['columnFormat'] = { # month column format, eg "Mon", see: # http://arshaw.com/fullcalendar/docs/text/columnFormat/ 'month': _('ddd'), # week column format: eg, "Mon 9/7", see: # http://arshaw.com/fullcalendar/docs/text/columnFormat/ 'week': _('ddd M/d'), # day column format : eg "Monday 9/7", see: # http://arshaw.com/fullcalendar/docs/text/columnFormat/ 'day': _('dddd M/d'), } options['timeFormat'] = { # for agendaWeek and agendaDay, eg "5:00 - 6:30", see: # http://arshaw.com/fullcalendar/docs/text/timeFormat/ 'agenda': _('h:mm{ - h:mm}'), # for all other views, eg "7p", see: # http://arshaw.com/fullcalendar/docs/text/timeFormat/ '': _('h(:mm)t'), } options['titleFormat'] = { # month title, eg "September 2009", see: # http://arshaw.com/fullcalendar/docs/text/titleFormat/ 'month': _('MMMM yyyy'), # week title, eg "Sep 7 - 13 2009" see: # http://arshaw.com/fullcalendar/docs/text/titleFormat/ 'week': _("MMM d[ yyyy]{ '—'[ MMM] d yyyy}"), # day time, eg "Tuesday, Sep 8, 2009" see: # http://arshaw.com/fullcalendar/docs/text/titleFormat/ 'day': _('dddd, MMM d, yyyy'), } if get_weekday_start() == MO: firstday = 1 else: firstday = 0 options['firstDay'] = firstday options['isRTL'] = ( gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL) options['data'] = self._show_events options['loading_msg'] = _('Loading calendar content, please wait...') self.js_function_call('startup', options) self._update_title()
def _generate_dailymovement_data(self, store): query = And(Payment.status.is_in([Payment.STATUS_PENDING, Payment.STATUS_PAID]), self._get_query(Payment.open_date, Payment.branch)) # Keys are the sale objects, and values are lists with all payments self.sales = collections.OrderedDict() # Keys are the returned sale objects, and values are lists with all payments self.return_sales = collections.OrderedDict() self.purchases = collections.OrderedDict() # lonely input and output payments self.lonely_in_payments = [] self.lonely_out_payments = [] # values are lists with the first element the summary of the input, and # the second the summary of the output method_summary = {} self.card_summary = {} result = store.find(DailyInPaymentView, query) for p in result.order_by(Sale.identifier, Payment.identifier): if p.sale: subtotal = p.sale_subtotal total = p.sale.get_total_sale_amount(subtotal) salesperson = p.salesperson_name or _('Not Specified') client = p.client_name or _('Not Specified') sale = DailyMovementSale(identifier=p.sale.identifier, salesperson=salesperson, client=client, branch=p.branch_name, value=get_formatted_price(total)) sale_payments = self.sales.setdefault(sale, {}) details = '' method_desc = p.method.get_description() if p.check_data: account = p.check_data.bank_account numbers = [payment.payment_number for payment in p.sale.payments if bool(payment.payment_number)] # Ensure that the check numbers are ordered numbers.sort() parts = [] if account.bank_number: parts.append(_(u'Bank: %s') % account.bank_number) if account.bank_branch: parts.append(_(u'Agency: %s') % account.bank_branch) if account.bank_account: parts.append(_(u'Account: %s') % account.bank_account) if numbers: parts.append(_(u'Numbers: %s') % ', '.join(numbers)) details = ' / '.join(parts) if p.card_data: if p.card_data.card_type == CreditCardData.TYPE_DEBIT: method_desc += ' ' + _('Debit') else: method_desc += ' ' + _(u'Credit') details = '%s - %s - %s' % (p.card_data.auth, p.card_data.provider.short_name or '', p.card_data.device.description or '') key = (method_desc, details) item = sale_payments.setdefault(key, [0, 0]) item[0] += p.value item[1] += 1 else: self.lonely_in_payments.append(p) method_summary.setdefault(p.method, [0, 0]) method_summary[p.method][0] += p.value if p.card_data: type_desc = p.card_data.short_desc[p.card_data.card_type] key = (p.card_data.provider.short_name, type_desc) self.card_summary.setdefault(key, 0) self.card_summary[key] += p.value result = store.find(DailyOutPaymentView, query) for p in result.order_by(Payment.identifier): if p.purchase: purchase_payments = self.purchases.setdefault(p.purchase, []) purchase_payments.append(p) elif p.sale: subtotal = p.sale_subtotal value = p.sale.get_total_sale_amount(subtotal) salesperson = p.salesperson_name or _('Not Specified') client = p.client_name or _('Not Specified') sale = DailyMovementSale(identifier=p.sale.identifier, salesperson=salesperson, client=client, return_date=p.sale.return_date, branch=p.branch_name, value=value) return_sales_payment = self.return_sales.setdefault(sale, []) return_sales_payment.append(p) else: self.lonely_out_payments.append(p) method_summary.setdefault(p.method, [0, 0]) method_summary[p.method][1] += p.value self.method_summary = [] for method, (in_value, out_value) in method_summary.items(): self.method_summary.append((method, in_value, out_value)) self.method_summary.sort(key=lambda m: _(m[0].description)) # Till removals query = And(Eq(TillEntry.payment_id, None), self._get_query(TillEntry.date, TillEntry.branch), TillEntry.value < 0) self.till_removals = store.find(TillEntry, query) # Till supply query = And(Eq(TillEntry.payment_id, None), self._get_query(TillEntry.date, TillEntry.branch), TillEntry.value > 0) self.till_supplies = store.find(TillEntry, query)
class _BackupStatus(ResourceStatus): name = "backup" label = _("Backup") priority = 98 def __init__(self): ResourceStatus.__init__(self) self._webservice = WebService() self._server = ServerProxy() @threaded def _get_key(self): return self._server.call('get_backup_key') @threaded def _get_server_status(self): request = self._webservice.status() return request.get_response() def refresh(self): if stoq.trial_mode: self.status = ResourceStatus.STATUS_NA self.reason = (_('Online features are not available in trial mode')) self.reason_long = _('Online features require a subscription of Stoq.link') return if not api.sysparam.get_bool('ONLINE_SERVICES'): self.status = ResourceStatus.STATUS_NA self.reason = _('Backup service not running because ' '"Online Services" is disabled') self.reason_long = _('Enable the parameter "Online Services" ' 'on the "Admin" app to solve this issue') return try: key = self._get_key() except ServerError: pass else: if not key: self.status = self.STATUS_WARNING self.reason = _("Backup key not configured") self.reason_long = _('Click on "Configure" button to ' 'configure the backup key') return try: response = self._get_server_status() except Exception as e: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = str(e) return if response.status_code != 200: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = None return data = response.json() if data['latest_backup_date']: backup_date = dateutil.parser.parse(data['latest_backup_date']) delta = datetime.datetime.today() - backup_date if delta.days > 3: self.status = self.STATUS_WARNING self.reason = _("Backup is late. Last backup date is %s") % ( backup_date.strftime('%x')) self.reason_long = _("Check your Stoq Server logs to see if " "there's any problem with it") else: self.status = self.STATUS_OK self.reason = _("Backup is up-to-date. Last backup date is %s") % ( backup_date.strftime('%x')) self.reason_long = None else: self.status = self.STATUS_WARNING self.reason = _("There's no backup data yet") self.reason_long = None def get_actions(self): if self.status != ResourceStatus.STATUS_NA: yield ResourceStatusAction( self, 'backup-now', _("Backup now"), self._on_backup_now, threaded=True) yield ResourceStatusAction( self, 'configure', _("Configure"), self._on_configure, threaded=False) def _on_configure(self): key = self._server.call('get_backup_key') with api.new_store() as store: rv = run_dialog(BackupSettingsEditor, None, store, Settable(key=key)) if rv: key = self._server.call('set_backup_key', rv.key) def _on_backup_now(self): self._server.call('backup_database')