Example #1
0
    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()
Example #2
0
    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()
Example #3
0
    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()
Example #4
0
    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()
Example #5
0
    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()
    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()
Example #7
0
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)
Example #8
0
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)
Example #9
0
    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()
Example #10
0
    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()
Example #11
0
 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()
Example #12
0
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))
Example #13
0
 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()
Example #14
0
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))