def __init__(self, store, model=None): BaseEditor.__init__(self, store, model) self.progress_dialog = ProgressDialog() self.progress_dialog.connect('cancel', self._on_progress_dialog__cancel) self.progress_dialog.set_transient_for(self.main_dialog) if self.edit_mode: self.printer.set_sensitive(False) self.main_dialog.ok_button.grab_focus() else: self.edit_constants.hide() self.device_serial.hide() self.device_serial_label.hide() self.is_active.hide()
def confirm(self, retval=None): total_products = len(self.mass_editor.get_changed_objects()) if total_products == 0: self.retval = False self.close() return retval = yesno( _('This will update {} products. Are you sure?'.format( total_products)), Gtk.ResponseType.NO, _('Apply changes'), _('Don\'t apply.')) if not retval: # Don't close the dialog. Let the user make more changes if he # wants to or cancel the dialog. return # FIXME: Is there a nicer way to display this progress? self.ok_button.set_sensitive(False) self.cancel_button.set_sensitive(False) d = ProgressDialog(_('Updating items'), pulse=False) d.set_transient_for(self) d.start(wait=0) d.cancel.hide() try: for i, total in self.mass_editor.confirm(self): d.progressbar.set_text('%s/%s' % (i + 1, total)) d.progressbar.set_fraction((i + 1) / float(total)) while Gtk.events_pending(): Gtk.main_iteration_do(False) except Exception as e: d.stop() self.retval = False log.error(''.join(traceback.format_exception(*sys.exc_info()))) warning(_('There was an error saving one of the values'), str(e)) self.close() return d.stop() self.retval = True self.close()
def __init__(self, store, model=None): self._device_manager = DeviceManager() BaseEditor.__init__(self, store, model) self.progress_dialog = ProgressDialog() self.progress_dialog.connect("cancel", self._on_progress_dialog__cancel) self.progress_dialog.set_transient_for(self.main_dialog) if self.edit_mode: self.printer.set_sensitive(False) self.main_dialog.ok_button.grab_focus() else: self.edit_constants.hide() self.device_serial.hide() self.device_serial_label.hide() self.is_active.hide()
def confirm(self, retval=None): total_products = len(self.mass_editor.get_changed_objects()) if total_products == 0: self.retval = False self.close() return retval = yesno( _('This will update {} products. Are you sure?'.format(total_products)), Gtk.ResponseType.NO, _('Apply changes'), _('Don\'t apply.')) if not retval: # Don't close the dialog. Let the user make more changes if he # wants to or cancel the dialog. return # FIXME: Is there a nicer way to display this progress? self.ok_button.set_sensitive(False) self.cancel_button.set_sensitive(False) d = ProgressDialog(_('Updating items'), pulse=False) d.set_transient_for(self) d.start(wait=0) d.cancel.hide() try: for i, total in self.mass_editor.confirm(self): d.progressbar.set_text('%s/%s' % (i + 1, total)) d.progressbar.set_fraction((i + 1) / float(total)) while Gtk.events_pending(): Gtk.main_iteration_do(False) except Exception as e: d.stop() self.retval = False log.error(''.join(traceback.format_exception(*sys.exc_info()))) warning(_('There was an error saving one of the values'), str(e)) self.close() return d.stop() self.retval = True self.close()
def on_confirm(self): marker('Saving prices') # FIXME: Improve this part. This is just a quick workaround to # release the bugfix asap self.main_dialog.ok_button.set_sensitive(False) self.main_dialog.cancel_button.set_sensitive(False) d = ProgressDialog(_('Updating items'), pulse=False) d.set_transient_for(self.main_dialog) d.start(wait=0) d.cancel.hide() total = len(self.slave.listcontainer.list) for i, s in enumerate(self.slave.listcontainer.list): s.save_changes() d.progressbar.set_text('%s/%s' % (i + 1, total)) d.progressbar.set_fraction((i + 1) / float(total)) while gtk.events_pending(): gtk.main_iteration(False) d.stop() marker('Done saving prices') self.slave.listcontainer.list.clear()
class ECFEditor(BaseEditor): translation_domain = 'stoq' domain = 'ecf' gladefile = 'FiscalPrinterDialog' model_type = ECFPrinter model_name = _('Fiscal Printer') proxy_widgets = [ 'device_name', 'device_serial', 'is_active', 'baudrate', 'user_number', 'register_date', 'register_cro' ] def __init__(self, store, model=None): self._device_manager = DeviceManager() BaseEditor.__init__(self, store, model) self.progress_dialog = ProgressDialog() self.progress_dialog.connect('cancel', self._on_progress_dialog__cancel) self.progress_dialog.set_transient_for(self.main_dialog) if self.edit_mode: self.printer.set_sensitive(False) self.main_dialog.ok_button.grab_focus() else: self.edit_constants.hide() self.device_serial.hide() self.device_serial_label.hide() self.is_active.hide() # # BaseEditor # def create_model(self, store): model = ECFPrinter(brand=u'daruma', model=u'FS345', device_name=u'/dev/ttyS0', device_serial=u'', baudrate=9600, station=get_current_station(store), is_active=True, store=store) if platform.system() == 'Windows': model.device_name = u'COM1' return model def setup_proxies(self): self._populate_printers() self._populate_serial_ports() self._populate_baudrate() self.proxy = self.add_proxy(self.model, ECFEditor.proxy_widgets) self.printer.select_item_by_label(self.model.get_description()) def validate_confirm(self): if not self.can_activate_printer(): return False if self.edit_mode: return True try: self._status = ECFAsyncPrinterStatus(self.model.device_name, self.model.printer_class, self.model.baudrate) except SerialException as e: warning(_('Error opening serial port'), str(e)) return False self._status.connect('reply', self._printer_status__reply) self._status.connect('timeout', self._printer_status__timeout) self.progress_dialog.set_label( _("Probing for a %s printer on %s") % (self.model.model_name, self._status.get_device_name())) self.progress_dialog.start() return False def can_activate_printer(self): serial = self.model.device_serial printers = self.store.find(ECFPrinter, is_active=True, station=get_current_station(self.store)) till = self.store.find(Till, status=Till.STATUS_OPEN, station=get_current_station(self.store)).one() if till and printers: warning( _("You need to close the till opened at %s before " "changing this printer.") % till.opening_date.date()) return False for p in printers: if p.device_serial != serial and self.model.is_active: warning( _(u'The ECF %s is already active for this ' 'station. Deactivate that printer before ' 'activating this one.') % p.model) return False return True # # Callbacks # def _on_progress_dialog__cancel(self, progress): # FIXME: # status.stop() pass def on_printer__content_changed(self, combo): # Cannot change the model in edit mode! if self.edit_mode: return printer = combo.get_selected() self.model.model = printer.model self.model.brand = printer.brand # These are not persistent self.model.model_name = printer.model_name self.model.printer_class = printer.printer_class def on_edit_constants__clicked(self, button): run_dialog(DeviceConstantsDialog, self, self.store, self.model) def _printer_status__reply(self, status, reply): self.progress_dialog.stop() if not reply: return if not self._populate_ecf_printer(status): return if yesno( _("An ECF Printer was added. You need to restart Stoq " "before using it. Would you like to restart it now?"), gtk.RESPONSE_YES, _("Restart now"), _("Restart later")): self.store.commit() raise SystemExit # FIXME: move to base dialogs or base editor self.retval = self.model self.main_dialog.close() def _printer_status__timeout(self, status): self.progress_dialog.stop() info( _("Could not find a %s printer connected to %s") % (self.model.model_name, status.get_device_name())) # # Private # def _populate_baudrate(self): values = get_baudrate_values() self.baudrate.prefill(values) def _populate_printers(self): supported_ifaces = get_supported_printers_by_iface( ICouponPrinter).items() printers = [] for brand, printer_classes in supported_ifaces: for printer_class in printer_classes: printer = _PrinterModel(brand, printer_class) printers.append((printer.get_description(), printer)) # Allow to use virtual printer for both demo mode and developer mode # so it's easier for testers and developers to test ecf functionality if sysparam.get_bool('DEMO_MODE') or is_developer_mode(): from stoqdrivers.printers.virtual.Simple import Simple printer = _PrinterModel('virtual', Simple) printers.append((printer.get_description(), printer)) self.printer.prefill( locale_sorted(printers, key=operator.itemgetter(0))) def _populate_serial_ports(self): values = [] for device in self._device_manager.get_serial_devices(): values.append(device.device_name) if not self.model.device_name in values: values.append(self.model.device_name) self.device_name.prefill(values) def _populate_ecf_printer(self, status): serial = unicode(status.printer.get_serial()) if self.store.find(ECFPrinter, device_serial=serial): status.stop() status.get_port().close() info(_("This printer is already known to the system")) return False self.model.device_serial = serial self._populate_constants(self.model, status) return True def _populate_constants(self, model, status): driver = status.get_driver() for tax_enum, device_value, value in driver.get_tax_constants(): if tax_enum == TaxType.CUSTOM: constant = self.store.find(SellableTaxConstant, tax_value=value).one() # If the constant is not defined in the system, create it if not constant: constant = SellableTaxConstant( tax_value=value, tax_type=int(TaxType.CUSTOM), description=u'%0.2f %%' % value, store=self.store) elif tax_enum == TaxType.SERVICE: constant = self.store.find(DeviceConstant, constant_enum=int(tax_enum), printer=model).one() # Skip, If we have a service tax defined for this printer # This needs to be improved when we support more than one # service tax if constant is not None: continue else: constant = self.store.find(SellableTaxConstant, tax_type=int(tax_enum)).one() # Ignore if its unkown tax if not constant: continue if value: constant_name = u'%0.2f %%' % (value, ) elif constant: constant_name = constant.description else: constant_name = None DeviceConstant(constant_enum=int(tax_enum), constant_name=constant_name, constant_type=DeviceConstant.TYPE_TAX, constant_value=value, device_value=device_value, printer=model, store=self.store) # This is going to be ugly, most printers don't support # a real constant for the payment methods, so we have to look # at the description and guess payment_enums = { 'dinheiro': PaymentMethodType.MONEY, 'cheque': PaymentMethodType.CHECK, 'boleto': PaymentMethodType.BILL, 'cartao credito': PaymentMethodType.CREDIT_CARD, 'cartao debito': PaymentMethodType.DEBIT_CARD, 'financeira': PaymentMethodType.FINANCIAL, 'vale compra': PaymentMethodType.GIFT_CERTIFICATE } payment_methods = [] for device_value, constant_name in driver.get_payment_constants(): lower = constant_name.lower() lower = lower.replace('é', 'e') # Workaround method names with lower = lower.replace('ã', 'a') # accents payment_enum = payment_enums.get(lower) if payment_enum is None: continue # Avoid register the same method twice for the same device if payment_enum in payment_methods: continue DeviceConstant(constant_enum=int(payment_enum), constant_name=unicode(constant_name), constant_type=DeviceConstant.TYPE_PAYMENT, constant_value=None, device_value=device_value, printer=model, store=self.store) payment_methods.append(payment_enum)
class ECFEditor(BaseEditor): translation_domain = 'stoq' domain = 'ecf' gladefile = 'FiscalPrinterDialog' model_type = ECFPrinter model_name = _('Fiscal Printer') proxy_widgets = ['device_name', 'device_serial', 'is_active', 'baudrate', 'user_number', 'register_date', 'register_cro'] def __init__(self, store, model=None): self._device_manager = DeviceManager() BaseEditor.__init__(self, store, model) self.progress_dialog = ProgressDialog() self.progress_dialog.connect('cancel', self._on_progress_dialog__cancel) self.progress_dialog.set_transient_for(self.main_dialog) if self.edit_mode: self.printer.set_sensitive(False) self.main_dialog.ok_button.grab_focus() else: self.edit_constants.hide() self.device_serial.hide() self.device_serial_label.hide() self.is_active.hide() # # BaseEditor # def create_model(self, store): model = ECFPrinter(brand=u'daruma', model=u'FS345', device_name=u'/dev/ttyS0', device_serial=u'', baudrate=9600, station=get_current_station(store), is_active=True, store=store) if platform.system() == 'Windows': model.device_name = u'COM1' return model def setup_proxies(self): self._populate_printers() self._populate_serial_ports() self._populate_baudrate() self.proxy = self.add_proxy(self.model, ECFEditor.proxy_widgets) self.printer.select_item_by_label(self.model.get_description()) def validate_confirm(self): if not self.can_activate_printer(): return False if self.edit_mode: return True try: self._status = ECFAsyncPrinterStatus(self.model.device_name, self.model.printer_class, self.model.baudrate) except SerialException as e: warning(_('Error opening serial port'), str(e)) return False self._status.connect('reply', self._printer_status__reply) self._status.connect('timeout', self._printer_status__timeout) self.progress_dialog.set_label(_("Probing for a %s printer on %s") % ( self.model.model_name, self._status.get_device_name())) self.progress_dialog.start() return False def can_activate_printer(self): serial = self.model.device_serial printers = self.store.find(ECFPrinter, is_active=True, station=get_current_station(self.store)) till = self.store.find(Till, status=Till.STATUS_OPEN, station=get_current_station(self.store)).one() if till and printers: warning(_("You need to close the till opened at %s before " "changing this printer.") % till.opening_date.date()) return False for p in printers: if p.device_serial != serial and self.model.is_active: warning(_(u'The ECF %s is already active for this ' 'station. Deactivate that printer before ' 'activating this one.') % p.model) return False return True # # Callbacks # def _on_progress_dialog__cancel(self, progress): # FIXME: # status.stop() pass def on_printer__content_changed(self, combo): # Cannot change the model in edit mode! if self.edit_mode: return printer = combo.get_selected() self.model.model = printer.model self.model.brand = printer.brand # These are not persistent self.model.model_name = printer.model_name self.model.printer_class = printer.printer_class def on_edit_constants__clicked(self, button): run_dialog(DeviceConstantsDialog, self, self.store, self.model) def _printer_status__reply(self, status, reply): self.progress_dialog.stop() if not reply: return if not self._populate_ecf_printer(status): return if yesno(_("An ECF Printer was added. You need to restart Stoq " "before using it. Would you like to restart it now?"), gtk.RESPONSE_YES, _("Restart now"), _("Restart later")): self.store.commit() raise SystemExit # FIXME: move to base dialogs or base editor self.retval = self.model self.main_dialog.close() def _printer_status__timeout(self, status): self.progress_dialog.stop() info(_("Could not find a %s printer connected to %s") % ( self.model.model_name, status.get_device_name())) # # Private # def _populate_baudrate(self): values = get_baudrate_values() self.baudrate.prefill(values) def _populate_printers(self): supported_ifaces = get_supported_printers_by_iface(ICouponPrinter).items() printers = [] for brand, printer_classes in supported_ifaces: for printer_class in printer_classes: printer = _PrinterModel(brand, printer_class) printers.append((printer.get_description(), printer)) # Allow to use virtual printer for both demo mode and developer mode # so it's easier for testers and developers to test ecf functionality if sysparam.get_bool('DEMO_MODE') or is_developer_mode(): from stoqdrivers.printers.virtual.Simple import Simple printer = _PrinterModel('virtual', Simple) printers.append((printer.get_description(), printer)) self.printer.prefill(locale_sorted( printers, key=operator.itemgetter(0))) def _populate_serial_ports(self): values = [] for device in self._device_manager.get_serial_devices(): values.append(device.device_name) if not self.model.device_name in values: values.append(self.model.device_name) self.device_name.prefill(values) def _populate_ecf_printer(self, status): serial = unicode(status.printer.get_serial()) if self.store.find(ECFPrinter, device_serial=serial): status.stop() status.get_port().close() info(_("This printer is already known to the system")) return False self.model.device_serial = serial self._populate_constants(self.model, status) return True def _populate_constants(self, model, status): driver = status.get_driver() for tax_enum, device_value, value in driver.get_tax_constants(): if tax_enum == TaxType.CUSTOM: constant = self.store.find(SellableTaxConstant, tax_value=value).one() # If the constant is not defined in the system, create it if not constant: constant = SellableTaxConstant(tax_value=value, tax_type=int(TaxType.CUSTOM), description=u'%0.2f %%' % value, store=self.store) elif tax_enum == TaxType.SERVICE: constant = self.store.find(DeviceConstant, constant_enum=int(tax_enum), printer=model).one() # Skip, If we have a service tax defined for this printer # This needs to be improved when we support more than one # service tax if constant is not None: continue else: constant = self.store.find(SellableTaxConstant, tax_type=int(tax_enum)).one() # Ignore if its unkown tax if not constant: continue if value: constant_name = u'%0.2f %%' % (value, ) elif constant: constant_name = constant.description else: constant_name = None DeviceConstant(constant_enum=int(tax_enum), constant_name=constant_name, constant_type=DeviceConstant.TYPE_TAX, constant_value=value, device_value=device_value, printer=model, store=self.store) # This is going to be ugly, most printers don't support # a real constant for the payment methods, so we have to look # at the description and guess payment_enums = {'dinheiro': PaymentMethodType.MONEY, 'cheque': PaymentMethodType.CHECK, 'boleto': PaymentMethodType.BILL, 'cartao credito': PaymentMethodType.CREDIT_CARD, 'cartao debito': PaymentMethodType.DEBIT_CARD, 'financeira': PaymentMethodType.FINANCIAL, 'vale compra': PaymentMethodType.GIFT_CERTIFICATE } payment_methods = [] for device_value, constant_name in driver.get_payment_constants(): lower = constant_name.lower() lower = lower.replace('é', 'e') # Workaround method names with lower = lower.replace('ã', 'a') # accents payment_enum = payment_enums.get(lower) if payment_enum is None: continue # Avoid register the same method twice for the same device if payment_enum in payment_methods: continue DeviceConstant(constant_enum=int(payment_enum), constant_name=unicode(constant_name), constant_type=DeviceConstant.TYPE_PAYMENT, constant_value=None, device_value=device_value, printer=model, store=self.store) payment_methods.append(payment_enum)
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()
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(False) progress_dialog.stop() self._update_ui()
def _create_progress(self, title): self._progress_dialog = ProgressDialog(title, pulse=False) self._progress_dialog.set_transient_for(self.main_dialog) self._progress_dialog.start(wait=0) self._progress_dialog.cancel.hide()
class PersonMergeDialog(BaseEditor): gladefile = "PersonMergeDialog" size = (800, 550) title = _(u'Duplicate Person Search') model_type = _MethodModel hide_footer = True proxy_widgets = ['method_combo', 'same_phone', 'same_street'] methods = [ (_('Identical name'), _MethodModel.SAME_NAME), (_('First and last name'), _MethodModel.FIRST_LAST_NAME), (_('First name'), _MethodModel.FIRST_NAME), ] def setup_proxies(self): self.dup_tree.set_columns(self._get_columns()) self.merge_button.set_sensitive(False) self.method_combo.prefill(self.methods) self.add_proxy(self.model, self.proxy_widgets) def create_model(self, store): return _MethodModel() # # Public API # def merge(self, store, to_merge): self._create_progress(_('Merging duplicate')) first = store.fetch(to_merge[0].person.person) rest = to_merge[1:] total = len(rest) for i, other in enumerate(rest): first.merge_with(store.fetch(other.person.person), copy_empty_values=True) self._update_progress(i, total) self._close_progress() # # Private API # def _create_progress(self, title): self._progress_dialog = ProgressDialog(title, pulse=False) self._progress_dialog.set_transient_for(self.main_dialog) self._progress_dialog.start(wait=0) self._progress_dialog.cancel.hide() def _update_progress(self, current, total): self._progress_dialog.progressbar.set_text('%s/%s' % (current, total)) self._progress_dialog.progressbar.set_fraction((current + 1) / float(total)) while gtk.events_pending(): gtk.main_iteration(False) def _close_progress(self): self._progress_dialog.stop() def _search_duplicates(self): self._create_progress(_('Searching duplicates')) data = self.store.find(PersonAddressView).order_by(PersonAddressView.name) total = data.count() self.dup_tree.clear() dups_total = 0 # A dictionaty mapping a reduced version of the person to a list of # persons that match this reduced version. person_dict = {} # A cache of duplicate registers already found. dups = [] for i, person in enumerate(data): key = self.model.get_key(person) if not key: continue entry = person_dict.setdefault(key, []) if len(entry) == 1: # The entry already has one person in it. So we can consider # this a duplicate. dups.append(entry) entry.append(person) if len(entry) >= 2: dups_total += 1 if i % 100 == 0: self._update_progress(i, total) self.message.set_text(_('Found %s persons in a total of %s duplicate registers') % (len(dups), dups_total + len(dups))) self._close_progress() self._build_duplicate_tree(dups) def _build_duplicate_tree(self, dups): for d in dups: root = _DupModel(name=d[0].name) self.dup_tree.append(None, root) for obj in d: model = _DupModel(person=obj, parent=root) root.add_dup(model) self.dup_tree.append(root, model) self.dup_tree.expand(root) def _get_columns(self): return [NameColumn(title=_('Name'), data_type=str, width=350), Column('cpf', title=_('CPF'), data_type=str), Column('cnpj', title=_('CNPJ'), data_type=str), Column('phone_number', title=_('Phone'), data_type=str, format_func=format_phone_number), Column('mobile_number', title=_('Mobile'), data_type=str, format_func=format_phone_number), Column('address', title=_('Address'), data_type=str, width=250), Column('fax_number', title=_('Fax'), data_type=str, format_func=format_phone_number), Column('email', title=_('Email'), data_type=str)] # # Callbacks # def on_search_button__clicked(self, widget): self._search_duplicates() def on_merge_button__clicked(self, widget): model = self.dup_tree.get_selected() to_merge = model.get_to_merge() msg = (_("This will merge %s persons into 1. Are you sure?") % len(to_merge)) if not yesno(msg, gtk.RESPONSE_NO, _("Merge"), _("Don't merge")): return with api.new_store() as store: self.merge(store, to_merge) if store.committed: self.dup_tree.remove(model) def on_dup_tree__selection_changed(self, olist, item): can_merge = item and len(item.get_to_merge()) > 1 self.merge_button.set_sensitive(bool(can_merge))
class PersonMergeDialog(BaseEditor): gladefile = "PersonMergeDialog" size = (800, 550) title = _(u'Duplicate Person Search') model_type = _MethodModel hide_footer = True proxy_widgets = ['method_combo', 'same_phone', 'same_street'] methods = [ (_('Identical name'), _MethodModel.SAME_NAME), (_('First and last name'), _MethodModel.FIRST_LAST_NAME), (_('First name'), _MethodModel.FIRST_NAME), ] def setup_proxies(self): self.dup_tree.set_columns(self._get_columns()) self.merge_button.set_sensitive(False) self.method_combo.prefill(self.methods) self.add_proxy(self.model, self.proxy_widgets) def create_model(self, store): return _MethodModel() # # Public API # def merge(self, store, to_merge): self._create_progress(_('Merging duplicate')) first = store.fetch(to_merge[0].person.person) rest = to_merge[1:] total = len(rest) for i, other in enumerate(rest): first.merge_with(store.fetch(other.person.person), copy_empty_values=True) self._update_progress(i, total) self._close_progress() # # Private API # def _create_progress(self, title): self._progress_dialog = ProgressDialog(title, pulse=False) self._progress_dialog.set_transient_for(self.main_dialog) self._progress_dialog.start(wait=0) self._progress_dialog.cancel.hide() def _update_progress(self, current, total): self._progress_dialog.progressbar.set_text('%s/%s' % (current, total)) self._progress_dialog.progressbar.set_fraction( (current + 1) / float(total)) while Gtk.events_pending(): Gtk.main_iteration_do(False) def _close_progress(self): self._progress_dialog.stop() def _search_duplicates(self): self._create_progress(_('Searching duplicates')) data = self.store.find(PersonAddressView).order_by( PersonAddressView.name) total = data.count() self.dup_tree.clear() dups_total = 0 # A dictionaty mapping a reduced version of the person to a list of # persons that match this reduced version. person_dict = {} # A cache of duplicate registers already found. dups = [] for i, person in enumerate(data): key = self.model.get_key(person) if not key: continue entry = person_dict.setdefault(key, []) if len(entry) == 1: # The entry already has one person in it. So we can consider # this a duplicate. dups.append(entry) entry.append(person) if len(entry) >= 2: dups_total += 1 if i % 100 == 0: self._update_progress(i, total) self.message.set_text( _('Found %s persons in a total of %s duplicate registers') % (len(dups), dups_total + len(dups))) self._close_progress() self._build_duplicate_tree(dups) def _build_duplicate_tree(self, dups): for d in dups: root = _DupModel(name=d[0].name) self.dup_tree.append(None, root) for obj in d: model = _DupModel(person=obj, parent=root) root.add_dup(model) self.dup_tree.append(root, model) self.dup_tree.expand(root) def _get_columns(self): return [ NameColumn(title=_('Name'), data_type=str, width=350), Column('cpf', title=_('CPF'), data_type=str), Column('cnpj', title=_('CNPJ'), data_type=str), Column('phone_number', title=_('Phone'), data_type=str, format_func=format_phone_number), Column('mobile_number', title=_('Mobile'), data_type=str, format_func=format_phone_number), Column('address', title=_('Address'), data_type=str, width=250), Column('fax_number', title=_('Fax'), data_type=str, format_func=format_phone_number), Column('email', title=_('Email'), data_type=str) ] # # Callbacks # def on_search_button__clicked(self, widget): self._search_duplicates() def on_merge_button__clicked(self, widget): model = self.dup_tree.get_selected() to_merge = model.get_to_merge() msg = (_("This will merge %s persons into 1. Are you sure?") % len(to_merge)) if not yesno(msg, Gtk.ResponseType.NO, _("Merge"), _("Don't merge")): return with api.new_store() as store: self.merge(store, to_merge) if store.committed: self.dup_tree.remove(model) def on_dup_tree__selection_changed(self, olist, item): can_merge = item and len(item.get_to_merge()) > 1 self.merge_button.set_sensitive(bool(can_merge))