Example #1
0
    def _setup_widgets(self):
        self._test_button = Gtk.Button(_("Print a test bill"))
        self._test_button.connect('clicked', self._on_test_button__clicked)
        self.parent_accounts = AccountTree(with_code=False, create_mode=True)
        self.parent_accounts.connect(
            'selection-changed', self._on_parent_accounts__selection_changed)
        self.tree_box.pack_start(self.parent_accounts, True, True, 0)
        self.tree_box.reorder_child(self.parent_accounts, 0)

        if sysparam.compare_object('IMBALANCE_ACCOUNT', self.model):
            self.account_type.set_sensitive(False)

        self.account_type.prefill(Account.account_type_descriptions)
        account_type = self.model.account_type

        self.parent_accounts.insert_initial(self.store,
                                            edited_account=self.model)
        if self.parent_account:
            account = self.parent_accounts.get_account_by_id(
                self.parent_account.id)
            self.parent_accounts.select(account)
            if not self.existing:
                account_type = account.account_type
        self.account_type.select(account_type)
        self.parent_accounts.show()
Example #2
0
 def __init__(self, window, store=None):
     # Account id -> TransactionPage
     self._pages = {}
     self.accounts = AccountTree()
     ShellApp.__init__(self, window, store=store)
     self._tills_account_id = api.sysparam.get_object_id('TILLS_ACCOUNT')
     self._imbalance_account_id = api.sysparam.get_object_id('IMBALANCE_ACCOUNT')
     self._banks_account_id = api.sysparam.get_object_id('BANKS_ACCOUNT')
Example #3
0
 def __init__(self, window, store=None):
     # Account id -> TransactionPage
     self._pages = {}
     self.accounts = AccountTree()
     ShellApp.__init__(self, window, store=store)
     self._tills_account = api.sysparam(self.store).TILLS_ACCOUNT
     self._imbalance_account = api.sysparam(self.store).IMBALANCE_ACCOUNT
     self._banks_account = api.sysparam(self.store).BANKS_ACCOUNT
Example #4
0
    def _setup_widgets(self):
        self._test_button = gtk.Button(_("Print a test bill"))
        self._test_button.connect('clicked',
                                  self._on_test_button__clicked)
        self.parent_accounts = AccountTree(with_code=False, create_mode=True)
        self.parent_accounts.connect('selection-changed',
                                     self._on_parent_accounts__selection_changed)
        self.tree_box.pack_start(self.parent_accounts)
        self.tree_box.reorder_child(self.parent_accounts, 0)

        if sysparam.compare_object('IMBALANCE_ACCOUNT', self.model):
            self.account_type.set_sensitive(False)

        self.account_type.prefill(Account.account_type_descriptions)
        account_type = self.model.account_type

        self.parent_accounts.insert_initial(self.store,
                                            edited_account=self.model)
        if self.parent_account:
            account = self.parent_accounts.get_account_by_id(
                self.parent_account.id)
            self.parent_accounts.select(account)
            if not self.existing:
                account_type = account.account_type
        self.account_type.select(account_type)
        self.parent_accounts.show()
Example #5
0
 def __init__(self, window, store=None):
     # Account id -> TransactionPage
     self._pages = {}
     self.accounts = AccountTree()
     ShellApp.__init__(self, window, store=store)
     self._tills_account_id = api.sysparam.get_object_id('TILLS_ACCOUNT')
     self._imbalance_account_id = api.sysparam.get_object_id('IMBALANCE_ACCOUNT')
     self._banks_account_id = api.sysparam.get_object_id('BANKS_ACCOUNT')
Example #6
0
 def __init__(self, window, store=None):
     # Account id -> TransactionPage
     self._pages = {}
     self.accounts = AccountTree()
     ShellApp.__init__(self, window, store=store)
     self._tills_account = api.sysparam(self.store).TILLS_ACCOUNT
     self._imbalance_account = api.sysparam(self.store).IMBALANCE_ACCOUNT
     self._banks_account = api.sysparam(self.store).BANKS_ACCOUNT
Example #7
0
class AccountEditor(BaseEditor):
    """ Account Editor """
    gladefile = "AccountEditor"
    proxy_widgets = ['description', 'code']
    model_type = Account
    model_name = _('Account')

    def __init__(self, store, model=None, parent_account=None):
        self._last_account_type = None
        self._bank_number = -1
        self._bank_widgets = []
        self._bank_option_widgets = []
        self._option_fields = {}
        self._test_button = None
        self.existing = model is not None
        self.parent_account = parent_account
        self.bank_model = _TemporaryBankAccount()
        BaseEditor.__init__(self, store, model)

        action_area = self.main_dialog.action_area
        action_area.set_layout(Gtk.ButtonBoxStyle.END)
        action_area.pack_start(self._test_button, False, False, 0)
        action_area.set_child_secondary(self._test_button, True)
        self._test_button.show()

    #
    # BaseEditor hooks
    #

    def create_model(self, store):
        return Account(description=u"",
                       account_type=Account.TYPE_CASH,
                       store=store)

    def _setup_widgets(self):
        self._test_button = Gtk.Button(_("Print a test bill"))
        self._test_button.connect('clicked', self._on_test_button__clicked)
        self.parent_accounts = AccountTree(with_code=False, create_mode=True)
        self.parent_accounts.connect(
            'selection-changed', self._on_parent_accounts__selection_changed)
        self.tree_box.pack_start(self.parent_accounts, True, True, 0)
        self.tree_box.reorder_child(self.parent_accounts, 0)

        if sysparam.compare_object('IMBALANCE_ACCOUNT', self.model):
            self.account_type.set_sensitive(False)

        self.account_type.prefill(Account.account_type_descriptions)
        account_type = self.model.account_type

        self.parent_accounts.insert_initial(self.store,
                                            edited_account=self.model)
        if self.parent_account:
            account = self.parent_accounts.get_account_by_id(
                self.parent_account.id)
            self.parent_accounts.select(account)
            if not self.existing:
                account_type = account.account_type
        self.account_type.select(account_type)
        self.parent_accounts.show()

    def setup_proxies(self):
        self._setup_widgets()
        self.add_proxy(self.model, AccountEditor.proxy_widgets)

    def validate_confirm(self):
        if not self.model.description:
            return False
        account = self.parent_accounts.get_selected()
        if not account:
            return True
        return account.selectable

    def on_confirm(self):
        new_parent = self.parent_accounts.get_selected()
        if new_parent:
            new_parent = new_parent.account
        if new_parent != self.model:
            self.model.parent = new_parent
        self.model.account_type = self.account_type.get_selected()
        self._save_bank()

    def refresh_ok(self, value):
        BaseEditor.refresh_ok(self, value)

        account_type = self.account_type.get_selected()
        if account_type != Account.TYPE_BANK:
            value = False
        self._test_button.set_sensitive(value)

    # Private

    def _save_bank(self):
        bank_account = self.model.bank
        if not bank_account:
            bank_account = BankAccount(account=self.model, store=self.store)
        # FIXME: Who sets this to a str?
        bank_account.bank_account = unicode(self.bank_model.bank_account)
        bank_account.bank_branch = unicode(self.bank_model.bank_branch)
        if self._bank_number is not None:
            bank_account.bank_number = self.bank_model.bank_number

        self._save_bank_bill_options(bank_account)

    def _save_bank_bill_options(self, bank_account):
        for option, entry in self._option_fields.items():
            value = unicode(entry.get_text())
            bill_option = self.store.find(BillOption,
                                          bank_account=bank_account,
                                          option=option).one()
            if bill_option is None:
                bill_option = BillOption(store=self.store,
                                         bank_account=bank_account,
                                         option=option,
                                         value=value)
            bill_option.value = value

    def _add_widget(self, label, widget, options=False):
        n_rows = self.table.props.n_rows
        l = Gtk.Label()
        l.set_markup(label)
        l.props.xalign = 1.0
        self.table.resize(n_rows + 1, 2)
        self.table.attach(l, 0, 1, n_rows, n_rows + 1, Gtk.AttachOptions.FILL,
                          0, 0, 0)
        self.table.attach(widget, 1, 2, n_rows, n_rows + 1,
                          Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0,
                          0, 0)
        if options:
            self._bank_option_widgets.extend([l, widget])
        else:
            self._bank_widgets.extend([l, widget])
        l.show()
        return l

    def _update_bank_type(self):
        self._remove_bank_option_widgets()

        bank_number = self.bank_type.get_selected()
        bank_info = None
        if bank_number:
            bank_info = get_bank_info_by_number(bank_number)

        self.bank_number = ProxyEntry()
        self.bank_number.props.data_type = int
        self.bank_number.set_sensitive(False)
        bank_number_lbl = self._add_widget(api.escape(_("Number:")),
                                           self.bank_number,
                                           options=True)

        self.bank_branch = ProxyEntry()
        self.bank_branch.connect('validate', self._on_bank_branch__validate,
                                 bank_info)
        self.bank_branch.props.data_type = 'str'
        self.bank_branch.props.mandatory = True
        self.bank_branch.model_attribute = "bank_branch"
        bank_branch_lbl = self._add_widget(api.escape(_("Agency:")),
                                           self.bank_branch,
                                           options=True)
        if bank_number is not None:
            bank_branch_lbl.show()
            self.bank_branch.show()
        else:
            bank_branch_lbl.hide()

        self.bank_account = ProxyEntry()
        self.bank_account.connect('validate', self._on_bank_account__validate,
                                  bank_info)
        self._add_widget(api.escape(_("Account:")),
                         self.bank_account,
                         options=True)
        self.bank_account.model_attribute = "bank_account"
        self.bank_account.props.data_type = 'str'
        if bank_number is not None:
            self.bank_account.props.mandatory = True
        self.bank_account.show()

        attributes = ['bank_account', 'bank_branch', 'bank_number']
        if bank_number is not None:
            bank_number_lbl.show()
            self.bank_number.show()

            self.bank_model.bank_number = bank_number

            for i, option in enumerate(bank_info.get_extra_options()):
                name = 'option' + str(i)
                entry = ProxyEntry()
                entry.model_attribute = name
                setattr(self, name, entry)
                # Set the model attr too so it can be validated
                setattr(self.bank_model, name, u'')
                entry.props.data_type = 'str'
                entry.connect('validate', self._on_bank_option__validate,
                              bank_info, option)
                name = option.replace('_', ' ').capitalize()
                self._add_widget("<i>%s</i>:" % api.escape(name),
                                 entry,
                                 options=True)
                entry.show()
                self._option_fields[option] = entry
                attributes.append(entry.model_attribute)
        else:
            bank_number_lbl.hide()

        self.bank_proxy = self.add_proxy(self.bank_model, attributes)
        self._fill_bank_account()

    def _fill_bank_account(self):
        if not self.model.bank:
            return

        self.bank_model.bank_branch = self.model.bank.bank_branch.encode(
            'utf-8')
        self.bank_model.bank_account = self.model.bank.bank_account.encode(
            'utf-8')
        self.bank_proxy.update('bank_branch')
        self.bank_proxy.update('bank_account')

        bill_options = list(
            self.store.find(BillOption, bank_account=self.model.bank))
        for bill_option in bill_options:
            if bill_option.option is None:
                continue
            field_entry = self._option_fields.get(bill_option.option)
            if field_entry:
                field_entry.set_text(bill_option.value)

    def _update_account_type(self, account_type):
        if not self.account_type.get_sensitive():
            return
        if account_type != Account.TYPE_BANK:
            self._remove_bank_widgets()
            self._remove_bank_option_widgets()
            self.code.set_sensitive(True)
            return

        self.code.set_sensitive(False)
        self.bank_type = ProxyComboBox()
        self._add_widget(api.escape(_("Bank:")), self.bank_type)
        self.bank_type.connect('content-changed',
                               self._on_bank_type__content_changed)
        self.bank_type.show()

        banks = [(_('Generic'), None)]
        banks.extend([(b.description, b.bank_number) for b in get_all_banks()])
        self.bank_type.prefill(banks)

        if self.model.bank:
            try:
                self.bank_type.select(self.model.bank.bank_number)
            except KeyError:
                self.bank_type.select(None)

        self._update_bank_type()

    def _remove_bank_widgets(self):
        for widget in self._bank_widgets:
            widget.get_parent().remove(widget)
            widget.destroy()
        self.table.resize(5, 2)
        self._bank_widgets = []

    def _remove_bank_option_widgets(self):
        for widget in self._bank_option_widgets:
            widget.get_parent().remove(widget)
            widget.destroy()
        self.table.resize(5 + len(self._bank_widgets) / 2, 2)
        self._bank_option_widgets = []
        self._option_fields = {}

    def _print_test_bill(self):
        try:
            bank_info = get_bank_info_by_number(self.bank_model.bank_number)
        except NotImplementedError:
            info(_("This bank does not support printing of bills"))
            return
        kwargs = dict(
            valor_documento=12345.67,
            data_vencimento=datetime.date.today(),
            data_documento=datetime.date.today(),
            data_processamento=datetime.date.today(),
            nosso_numero=u'24533',
            numero_documento=u'1138',
            sacado=[_(u"Drawee"), _(u"Address"),
                    _(u"Details")],
            cedente=[_(u"Supplier"),
                     _(u"Address"),
                     _(u"Details"), ("CNPJ")],
            demonstrativo=[_(u"Demonstration")],
            instrucoes=[_(u"Instructions")],
            agencia=self.bank_model.bank_branch,
            conta=self.bank_model.bank_account,
        )
        for opt in self.bank_model.options:
            kwargs[opt.option] = opt.value
        data = bank_info(**kwargs)
        print_report(BillTestReport, data)

    # Callbacks

    def _on_parent_accounts__selection_changed(self, objectlist, account):
        self.force_validation()

    def on_description__activate(self, entry):
        if self.validate_confirm():
            self.confirm()

    def on_description__validate(self, entry, text):
        if not text:
            return ValidationError(_("Account description cannot be empty"))

    def on_account_type__content_changed(self, account_type):
        account_type = account_type.get_selected()
        if self._last_account_type == account_type:
            return
        self._update_account_type(account_type)
        self._last_account_type = account_type

    def _on_bank_type__content_changed(self, bank_type):
        bank_number = bank_type.get_selected()
        if self._bank_number == bank_number:
            return
        self._update_bank_type()

        self._bank_number = bank_number

    def _on_bank_branch__validate(self, entry, value, bank_info):
        if bank_info:
            try:
                bank_info.validate_field(value)
            except BoletoException as e:
                return ValidationError(str(e))

    def _on_bank_account__validate(self, entry, value, bank_info):
        if bank_info:
            try:
                bank_info.validate_field(value)
            except BoletoException as e:
                return ValidationError(str(e))

    def _on_bank_option__validate(self, entry, value, bank_info, option):
        try:
            bank_info.validate_option(option, value)
        except BoletoException as e:
            return ValidationError(str(e))
        self.bank_model.set_option(option, value)

    def _on_test_button__clicked(self, button):
        self._print_test_bill()
Example #8
0
class FinancialApp(ShellApp):

    app_title = _('Financial')
    gladefile = 'financial'

    def __init__(self, window, store=None):
        # Account id -> TransactionPage
        self._pages = {}
        self.accounts = AccountTree()
        ShellApp.__init__(self, window, store=store)
        self._tills_account_id = api.sysparam.get_object_id('TILLS_ACCOUNT')
        self._imbalance_account_id = api.sysparam.get_object_id('IMBALANCE_ACCOUNT')
        self._banks_account_id = api.sysparam.get_object_id('BANKS_ACCOUNT')

    #
    # ShellApp overrides
    #

    def create_actions(self):
        group = get_accels('app.financial')
        actions = [
            ('TransactionMenu', None, _('Transaction')),
            ('AccountMenu', None, _('Account')),
            ('Import', gtk.STOCK_ADD, _('Import...'),
             group.get('import'), _('Import a GnuCash or OFX file')),
            ('ConfigurePaymentMethods', None,
             _('Payment methods'),
             group.get('configure_payment_methods'),
             _('Select accounts for the payment methods on the system')),
            ('DeleteAccount', gtk.STOCK_DELETE, _('Delete...'),
             group.get('delete_account'),
             _('Delete the selected account')),
            ('DeleteTransaction', gtk.STOCK_DELETE, _('Delete...'),
             group.get('delete_transaction'),
             _('Delete the selected transaction')),
            ("NewAccount", gtk.STOCK_NEW, _("Account..."),
             group.get('new_account'),
             _("Add a new account")),
            ("NewTransaction", gtk.STOCK_NEW, _("Transaction..."),
             group.get('new_store'),
             _("Add a new transaction")),
            ("Edit", gtk.STOCK_EDIT, _("Edit..."),
             group.get('edit')),
        ]
        self.financial_ui = self.add_ui_actions('', actions,
                                                filename='financial.xml')
        self.set_help_section(_("Financial help"), 'app-financial')
        self.Edit.set_short_label(_('Edit'))
        self.DeleteAccount.set_short_label(_('Delete'))
        self.DeleteTransaction.set_short_label(_('Delete'))

        user = api.get_current_user(self.store)
        if not user.profile.check_app_permission(u'admin'):
            self.ConfigurePaymentMethods.set_sensitive(False)

    def create_ui(self):
        self.trans_popup = self.uimanager.get_widget('/TransactionSelection')
        self.acc_popup = self.uimanager.get_widget('/AccountSelection')

        self.window.add_new_items([self.NewAccount,
                                   self.NewTransaction])

        self.search_holder.add(self.accounts)
        self.accounts.show()
        self._create_initial_page()
        self._refresh_accounts()

    def activate(self, refresh=True):
        if refresh:
            for page in self._pages.values():
                page.refresh()
        self._update_actions()
        self._update_tooltips()
        self.window.SearchToolItem.set_sensitive(False)

    def deactivate(self):
        self.uimanager.remove_ui(self.financial_ui)
        self.window.SearchToolItem.set_sensitive(True)

    def print_activate(self):
        self._print_transaction_report()

    def export_spreadsheet_activate(self):
        self._export_spreadsheet()

    def get_current_page(self):
        widget = self._get_current_page_widget()
        if hasattr(widget, 'page'):
            return widget.page

    #
    # Private
    #

    def _update_actions(self):
        is_accounts_tab = self._is_accounts_tab()
        self.AccountMenu.set_visible(is_accounts_tab)
        self.TransactionMenu.set_visible(not is_accounts_tab)
        self.DeleteAccount.set_visible(is_accounts_tab)
        self.DeleteTransaction.set_visible(not is_accounts_tab)
        self.window.ExportSpreadSheet.set_sensitive(True)
        self.window.Print.set_sensitive(not is_accounts_tab)

        self.NewAccount.set_sensitive(self._can_add_account())
        self.DeleteAccount.set_sensitive(self._can_delete_account())
        self.NewTransaction.set_sensitive(self._can_add_transaction())
        self.DeleteTransaction.set_sensitive(self._can_delete_transaction())
        self.Edit.set_sensitive(self._can_edit_account() or
                                self._can_edit_transaction())

    def _update_tooltips(self):
        if self._is_accounts_tab():
            self.Edit.set_tooltip(_("Edit the selected account"))
            self.window.Print.set_tooltip("")
        else:
            self.Edit.set_tooltip(_("Edit the selected transaction"))
            self.window.Print.set_tooltip(
                _("Print a report of these transactions"))

    def _create_initial_page(self):
        pixbuf = self.accounts.render_icon('stoq-money', gtk.ICON_SIZE_MENU)
        page = self.notebook.get_nth_page(0)
        hbox = self._create_tab_label(_('Accounts'), pixbuf)
        self.notebook.set_tab_label(page, hbox)

    def _create_new_account(self):
        parent_view = None
        if self._is_accounts_tab():
            parent_view = self.accounts.get_selected()
        else:
            page_id = self.notebook.get_current_page()
            widget = self.notebook.get_nth_page(page_id)
            page = widget.page
            if page.account_view.kind == 'account':
                parent_view = page.account_view
        retval = self._run_account_editor(None, parent_view)
        if retval:
            self.accounts.refresh_accounts(self.store)

    def _refresh_accounts(self):
        self.accounts.clear()
        self.accounts.insert_initial(self.store)

    def _edit_existing_account(self, account_view):
        assert account_view.kind == 'account'
        retval = self._run_account_editor(account_view,
                                          self.accounts.get_parent(account_view))
        if not retval:
            return
        self.accounts.refresh_accounts(self.store)

    def _run_account_editor(self, model, parent_account):
        store = api.new_store()
        if model:
            model = store.fetch(model.account)
        if parent_account:
            if parent_account.kind in ['payable', 'receivable']:
                parent_account = None
            if api.sysparam.compare_object('IMBALANCE_ACCOUNT', parent_account):
                parent_account = None
        retval = self.run_dialog(AccountEditor, store, model=model,
                                 parent_account=parent_account)
        if store.confirm(retval):
            self.accounts.refresh_accounts(self.store)
        store.close()

        return retval

    def _close_current_page(self):
        assert self._can_close_tab()

        page = self.get_current_page()
        self._close_page(page)

    def _get_current_page_widget(self):
        page_id = self.notebook.get_current_page()
        widget = self.notebook.get_children()[page_id]
        return widget

    def _close_page(self, page):
        for page_id, child in enumerate(self.notebook.get_children()):
            if getattr(child, 'page', None) == page:
                self.notebook.remove_page(page_id)
                del self._pages[page.account_view.id]
                break
        else:
            raise AssertionError(page)

    def _is_accounts_tab(self):
        page_id = self.notebook.get_current_page()
        return page_id == 0

    def _is_transaction_tab(self):
        page = self.get_current_page()
        if not isinstance(page, TransactionPage):
            return False

        if page.model.kind != 'account':
            return False

        if (api.sysparam.compare_object('TILLS_ACCOUNT', page.model.account) or
            page.model.parent_id == self._tills_account_id):
            return False
        return True

    def _can_close_tab(self):
        # The first tab is not closable
        return not self._is_accounts_tab()

    def _create_tab_label(self, title, pixbuf, page=None):
        hbox = gtk.HBox()
        image = gtk.image_new_from_pixbuf(pixbuf)
        hbox.pack_start(image, False, False)
        label = gtk.Label(title)
        hbox.pack_start(label, True, False)
        if title != _("Accounts"):
            button = NotebookCloseButton()
            if page:
                button.connect('clicked', lambda button: self._close_page(page))
            hbox.pack_end(button, False, False)
        hbox.show_all()
        return hbox

    def _new_page(self, account_view):
        if account_view.id in self._pages:
            page = self._pages[account_view.id]
            page_id = self.notebook.page_num(page.search.vbox)
        else:
            pixbuf = self.accounts.get_pixbuf(account_view)
            page = TransactionPage(account_view,
                                   self, self.get_toplevel())
            page.search.connect('result-selection-changed',
                                self._on_search__result_selection_changed)
            page.search.connect('result-item-popup-menu',
                                self._on_search__result_item_popup_menu)
            hbox = self._create_tab_label(account_view.description, pixbuf, page)
            widget = page.search.vbox
            widget.page = page
            page_id = self.notebook.append_page(widget, hbox)
            page.show()
            page.account_view = account_view
            self._pages[account_view.id] = page

        self.notebook.set_current_page(page_id)
        self._update_actions()

    def _import(self):
        ffilters = []

        all_filter = gtk.FileFilter()
        all_filter.set_name(_('All supported formats'))
        all_filter.add_pattern('*.ofx')
        all_filter.add_mime_type('application/xml')
        all_filter.add_mime_type('application/x-gzip')
        ffilters.append(all_filter)

        ofx_filter = gtk.FileFilter()
        ofx_filter.set_name(_('Open Financial Exchange (OFX)'))
        ofx_filter.add_pattern('*.ofx')
        ffilters.append(ofx_filter)

        gnucash_filter = gtk.FileFilter()
        gnucash_filter.set_name(_('GNUCash xml format'))
        gnucash_filter.add_mime_type('application/xml')
        gnucash_filter.add_mime_type('application/x-gzip')
        ffilters.append(gnucash_filter)

        with selectfile("Import", parent=self.get_toplevel(),
                        filters=ffilters) as file_chooser:
            file_chooser.run()

            filename = file_chooser.get_filename()
            if not filename:
                return

            ffilter = file_chooser.get_filter()
            if ffilter == gnucash_filter:
                format = 'gnucash.xml'
            elif ffilter == ofx_filter:
                format = 'account.ofx'
            else:
                # Guess
                if filename.endswith('.ofx'):
                    format = 'account.ofx'
                else:
                    format = 'gnucash.xml'
        run_dialog(ImporterDialog, self, format, filename)

        # Refresh everthing after an import
        self.accounts.refresh_accounts(self.store)
        for page in self._pages.values():
            page.refresh()

    def _export_spreadsheet(self):
        """Runs a dialog to export the current search results to a CSV file.
        """
        if self._is_accounts_tab():
            run_dialog(FinancialReportDialog, self, self.store)
        else:
            page = self.get_current_page()
            sse = SpreadSheetExporter()
            sse.export(object_list=page.result_view,
                       name=self.app_title,
                       filename_prefix=self.app_name)

    def _can_add_account(self):
        if self._is_accounts_tab():
            return True

        return False

    def _can_edit_account(self):
        if not self._is_accounts_tab():
            return False

        account_view = self.accounts.get_selected()
        if account_view is None:
            return False

        # Can only remove real accounts
        if account_view.kind != 'account':
            return False

        if account_view.id in [self._banks_account_id,
                               self._imbalance_account_id,
                               self._tills_account_id]:
            return False
        return True

    def _can_delete_account(self):
        if not self._is_accounts_tab():
            return False

        account_view = self.accounts.get_selected()
        if account_view is None:
            return False

        # Can only remove real accounts
        if account_view.kind != 'account':
            return False

        return account_view.account.can_remove()

    def _can_add_transaction(self):
        if self._is_transaction_tab():
            return True
        return False

    def _can_delete_transaction(self):
        if not self._is_transaction_tab():
            return False

        page = self.get_current_page()
        transaction = page.result_view.get_selected()
        if transaction is None:
            return False

        return True

    def _can_edit_transaction(self):
        if not self._is_transaction_tab():
            return False

        page = self.get_current_page()
        transaction = page.result_view.get_selected()
        if transaction is None:
            return False

        return True

    def _add_transaction(self):
        page = self.get_current_page()
        page.add_transaction_dialog()
        self._refresh_accounts()

    def _delete_account(self, account_view):
        store = api.new_store()
        account = store.fetch(account_view.account)
        methods = PaymentMethod.get_by_account(store, account)
        if methods.count() > 0:
            if not yesno(
                _('This account is used in at least one payment method.\n'
                  'To be able to delete it the payment methods needs to be'
                  're-configured first'), gtk.RESPONSE_NO,
                _("Configure payment methods"), _("Keep account")):
                store.close()
                return
        elif not yesno(
            _('Are you sure you want to remove account "%s" ?') % (
                (account_view.description, )), gtk.RESPONSE_NO,
            _("Remove account"), _("Keep account")):
            store.close()
            return

        if account_view.id in self._pages:
            account_page = self._pages[account_view.id]
            self._close_page(account_page)

        self.accounts.remove(account_view)
        self.accounts.flush()

        imbalance_id = api.sysparam.get_object_id('IMBALANCE_ACCOUNT')
        for method in methods:
            method.destination_account_id = imbalance_id

        account.remove(store)
        store.commit(close=True)

    def _delete_transaction(self, item):
        msg = _('Are you sure you want to remove transaction "%s" ?') % (
            (item.description))
        if not yesno(msg, gtk.RESPONSE_YES,
                     _(u"Remove transaction"), _(u"Keep transaction")):
            return

        account_transactions = self.get_current_page()
        account_transactions.result_view.remove(item)

        store = api.new_store()
        if isinstance(item.transaction, AccountTransactionView):
            account_transaction = store.fetch(item.transaction.transaction)
        else:
            account_transaction = store.fetch(item.transaction)
        account_transaction.delete(account_transaction.id, store=store)
        store.commit(close=True)
        account_transactions.update_totals()

    def _print_transaction_report(self):
        assert not self._is_accounts_tab()

        page = self.get_current_page()
        print_report(AccountTransactionReport, page.result_view,
                     list(page.result_view),
                     account=page.model,
                     filters=page.search.get_search_filters())

    #
    # Kiwi callbacks
    #

    def key_escape(self):
        if self._can_close_tab():
            self._close_current_page()
        return True

    def key_control_w(self):
        if self._can_close_tab():
            self._close_current_page()
        return True

    def on_accounts__row_activated(self, ktree, account_view):
        self._new_page(account_view)

    def on_accounts__selection_changed(self, ktree, account_view):
        self._update_actions()

    def on_accounts__right_click(self, results, result, event):
        self.acc_popup.popup(None, None, None, event.button, event.time)

    def on_Edit__activate(self, button):
        if self._is_accounts_tab():
            account_view = self.accounts.get_selected()
            self._edit_existing_account(account_view)
        elif self._is_transaction_tab():
            page = self.get_current_page()
            transaction = page.result_view.get_selected()
            page._edit_transaction_dialog(transaction)

    def after_notebook__switch_page(self, notebook, page, page_id):
        self._update_actions()
        self._update_tooltips()

    def _on_search__result_selection_changed(self, search):
        self._update_actions()

    def _on_search__result_item_popup_menu(self, search, result, event):
        self.trans_popup.popup(None, None, None, event.button, event.time)

    # Toolbar

    def new_activate(self):
        if self._is_accounts_tab() and self._can_add_account():
            self._create_new_account()
        elif self._is_transaction_tab() and self._can_add_transaction():
            self._add_transaction()

    def on_NewAccount__activate(self, action):
        self._create_new_account()

    def on_NewTransaction__activate(self, action):
        self._add_transaction()

    def on_DeleteAccount__activate(self, action):
        account_view = self.accounts.get_selected()
        self._delete_account(account_view)

    def on_DeleteTransaction__activate(self, action):
        transactions = self.get_current_page()
        transaction = transactions.result_view.get_selected()
        self._delete_transaction(transaction)
        self._refresh_accounts()

    # Financial

    def on_Import__activate(self, action):
        self._import()

    # Edit
    def on_ConfigurePaymentMethods__activate(self, action):
        from stoqlib.gui.dialogs.paymentmethod import PaymentMethodsDialog
        store = api.new_store()
        model = self.run_dialog(PaymentMethodsDialog, store)
        store.confirm(model)
        store.close()
Example #9
0
class FinancialApp(ShellApp):

    app_title = _('Financial')
    gladefile = 'financial'

    def __init__(self, window, store=None):
        # Account id -> TransactionPage
        self._pages = {}
        self.accounts = AccountTree()
        ShellApp.__init__(self, window, store=store)
        self._tills_account_id = api.sysparam.get_object_id('TILLS_ACCOUNT')
        self._imbalance_account_id = api.sysparam.get_object_id('IMBALANCE_ACCOUNT')
        self._banks_account_id = api.sysparam.get_object_id('BANKS_ACCOUNT')

    #
    # ShellApp overrides
    #

    def create_actions(self):
        group = get_accels('app.financial')
        actions = [
            ('Import', Gtk.STOCK_ADD, _('Import...'),
             group.get('import'), _('Import a GnuCash or OFX file')),
            ('ConfigurePaymentMethods', None,
             _('Payment methods'),
             group.get('configure_payment_methods'),
             _('Select accounts for the payment methods on the system')),
            ('Delete', None, _('Delete...')),
            ("NewAccount", Gtk.STOCK_NEW, _("Account..."),
             group.get('new_account'),
             _("Add a new account")),
            ("NewTransaction", Gtk.STOCK_NEW, _("Transaction..."),
             group.get('new_store'),
             _("Add a new transaction")),
            ("Edit", Gtk.STOCK_EDIT, _("Edit..."),
             group.get('edit')),
        ]
        self.financial_ui = self.add_ui_actions(actions)
        self.set_help_section(_("Financial help"), 'app-financial')

        user = api.get_current_user(self.store)
        if not user.profile.check_app_permission(u'admin'):
            self.set_sensitive([self.ConfigurePaymentMethods], False)

    def get_domain_options(self):
        options = [
            ('fa-edit-symbolic', _('Edit'), 'financial.Edit', True),
            ('fa-trash-alt-symbolic', _('Delete'), 'financial.Delete', True),
        ]
        return options

    def create_ui(self):
        self.window.add_print_items()
        self.window.add_new_items([self.NewAccount,
                                   self.NewTransaction])

        self.window.add_export_items([self.Import])
        self.window.add_extra_items(
            [self.ConfigurePaymentMethods]
        )

        self.search_holder.add(self.accounts)
        self.accounts.show()
        self._create_initial_page()
        self._refresh_accounts()

    def activate(self, refresh=True):
        if refresh:
            self.refresh_pages()
        self._update_actions()
        self._update_tooltips()

    def print_activate(self):
        self._print_transaction_report()

    def export_spreadsheet_activate(self):
        self._export_spreadsheet()

    def get_current_page(self):
        widget = self._get_current_page_widget()
        if hasattr(widget, 'page'):
            return widget.page

    #
    # Private
    #

    def _update_actions(self):
        is_accounts_tab = self._is_accounts_tab()
        self.set_sensitive([self.window.print], not is_accounts_tab)

        self.set_sensitive([self.Delete], self._can_delete_transaction() or
                           self._can_delete_account())

        self.set_sensitive([self.NewAccount], self._can_add_account())
        self.set_sensitive([self.NewTransaction], self._can_add_transaction())
        self.set_sensitive([self.Edit],
                           self._can_edit_account() or self._can_edit_transaction())

    def _update_tooltips(self):
        edit_btn = self.window.domain_header.get_children()[0]
        if self._is_accounts_tab():
            edit_btn.set_tooltip_text(_("Edit the selected account"))
        else:
            edit_btn.set_tooltip_text(_("Edit the selected transaction"))

    def _create_initial_page(self):
        pixbuf = self.accounts.render_icon('stoq-money', Gtk.IconSize.MENU)
        page = self.notebook.get_nth_page(0)
        hbox = self._create_tab_label(_('Accounts'), pixbuf)
        self.notebook.set_tab_label(page, hbox)

    def _create_new_account(self):
        parent_view = None
        if self._is_accounts_tab():
            parent_view = self.accounts.get_selected()
        else:
            page_id = self.notebook.get_current_page()
            widget = self.notebook.get_nth_page(page_id)
            page = widget.page
            if page.account_view.kind == 'account':
                parent_view = page.account_view
        retval = self._run_account_editor(None, parent_view)
        if retval:
            self.accounts.refresh_accounts(self.store)

    def _refresh_accounts(self):
        self.accounts.clear()
        self.accounts.insert_initial(self.store)

    def _edit_existing_account(self, account_view):
        assert account_view.kind == 'account'
        retval = self._run_account_editor(account_view,
                                          self.accounts.get_parent(account_view))
        if not retval:
            return
        self.accounts.refresh_accounts(self.store)

    def _run_account_editor(self, model, parent_account):
        store = api.new_store()
        if model:
            model = store.fetch(model.account)
        if parent_account:
            if parent_account.kind in ['payable', 'receivable']:
                parent_account = None
            if api.sysparam.compare_object('IMBALANCE_ACCOUNT', parent_account):
                parent_account = None
        retval = self.run_dialog(AccountEditor, store, model=model,
                                 parent_account=parent_account)
        if store.confirm(retval):
            self.accounts.refresh_accounts(self.store)
        store.close()

        return retval

    def _close_current_page(self):
        assert self._can_close_tab()

        page = self.get_current_page()
        self._close_page(page)

    def _get_current_page_widget(self):
        page_id = self.notebook.get_current_page()
        widget = self.notebook.get_children()[page_id]
        return widget

    def _close_page(self, page):
        for page_id, child in enumerate(self.notebook.get_children()):
            if getattr(child, 'page', None) == page:
                self.notebook.remove_page(page_id)
                del self._pages[page.account_view.id]
                break
        else:
            raise AssertionError(page)

    def _is_accounts_tab(self):
        page_id = self.notebook.get_current_page()
        return page_id == 0

    def _is_transaction_tab(self):
        page = self.get_current_page()
        if not isinstance(page, TransactionPage):
            return False

        if page.model.kind != 'account':
            return False

        if (api.sysparam.compare_object('TILLS_ACCOUNT', page.model.account) or
                page.model.parent_id == self._tills_account_id):
            return False
        return True

    def _can_close_tab(self):
        # The first tab is not closable
        return not self._is_accounts_tab()

    def _create_tab_label(self, title, pixbuf, account_view_id=None, page=None):
        hbox = Gtk.HBox()
        image = Gtk.Image.new_from_pixbuf(pixbuf)
        hbox.pack_start(image, False, False, 0)
        label = Gtk.Label(label=title)
        hbox.pack_start(label, True, False, 0)
        if account_view_id:
            button = NotebookCloseButton()
            if page:
                button.connect('clicked', lambda button: self._close_page(page))
            hbox.pack_end(button, False, False, 0)
        hbox.show_all()
        return hbox

    def _new_page(self, account_view):
        if account_view.id in self._pages:
            page = self._pages[account_view.id]
            page_id = self.notebook.page_num(page.search.vbox)
        else:
            pixbuf = self.accounts.get_pixbuf(account_view)
            page = TransactionPage(account_view,
                                   self, self.get_toplevel())
            page.search.connect('result-selection-changed',
                                self._on_search__result_selection_changed)
            page.search.connect('result-item-popup-menu',
                                self._on_search__result_item_popup_menu)
            hbox = self._create_tab_label(account_view.description, pixbuf,
                                          account_view.id, page)
            widget = page.search.vbox
            widget.page = page
            page_id = self.notebook.append_page(widget, hbox)
            page.show()
            page.account_view = account_view
            self._pages[account_view.id] = page

        self.notebook.set_current_page(page_id)
        self._update_actions()

    def refresh_pages(self):
        for page in self._pages.values():
            page.refresh()

    def _import(self):
        ffilters = []

        all_filter = Gtk.FileFilter()
        all_filter.set_name(_('All supported formats'))
        all_filter.add_pattern('*.ofx')
        all_filter.add_mime_type('application/xml')
        all_filter.add_mime_type('application/x-gzip')
        ffilters.append(all_filter)

        ofx_filter = Gtk.FileFilter()
        ofx_filter.set_name(_('Open Financial Exchange (OFX)'))
        ofx_filter.add_pattern('*.ofx')
        ffilters.append(ofx_filter)

        gnucash_filter = Gtk.FileFilter()
        gnucash_filter.set_name(_('GNUCash xml format'))
        gnucash_filter.add_mime_type('application/xml')
        gnucash_filter.add_mime_type('application/x-gzip')
        ffilters.append(gnucash_filter)

        with selectfile("Import", parent=self.get_toplevel(),
                        filters=ffilters) as file_chooser:
            file_chooser.run()

            filename = file_chooser.get_filename()
            if not filename:
                return

            ffilter = file_chooser.get_filter()
            if ffilter == gnucash_filter:
                format = 'gnucash.xml'
            elif ffilter == ofx_filter:
                format = 'account.ofx'
            else:
                # Guess
                if filename.endswith('.ofx'):
                    format = 'account.ofx'
                else:
                    format = 'gnucash.xml'
        run_dialog(ImporterDialog, self, format, filename)

        # Refresh everthing after an import
        self.accounts.refresh_accounts(self.store)
        self.refresh_pages()

    def _export_spreadsheet(self):
        """Runs a dialog to export the current search results to a CSV file.
        """
        if self._is_accounts_tab():
            run_dialog(FinancialReportDialog, self, self.store)
        else:
            page = self.get_current_page()
            sse = SpreadSheetExporter()
            sse.export(object_list=page.result_view,
                       name=self.app_title,
                       filename_prefix=self.app_name)

    def _can_add_account(self):
        if self._is_accounts_tab():
            return True

        return False

    def _can_edit_account(self):
        if not self._is_accounts_tab():
            return False

        account_view = self.accounts.get_selected()
        if account_view is None:
            return False

        # Can only remove real accounts
        if account_view.kind != 'account':
            return False

        if account_view.id in [self._banks_account_id,
                               self._imbalance_account_id,
                               self._tills_account_id]:
            return False
        return True

    def _can_delete_account(self):
        if not self._is_accounts_tab():
            return False

        account_view = self.accounts.get_selected()
        if account_view is None:
            return False

        # Can only remove real accounts
        if account_view.kind != 'account':
            return False

        return account_view.account.can_remove()

    def _can_add_transaction(self):
        if self._is_transaction_tab():
            return True
        return False

    def _can_delete_transaction(self):
        if not self._is_transaction_tab():
            return False

        page = self.get_current_page()
        transaction = page.result_view.get_selected()
        if transaction is None:
            return False

        return True

    def _can_edit_transaction(self):
        if not self._is_transaction_tab():
            return False

        page = self.get_current_page()
        transaction = page.result_view.get_selected()
        if transaction is None:
            return False

        return True

    def _add_transaction(self):
        page = self.get_current_page()
        page.add_transaction_dialog()
        self._refresh_accounts()

    def _delete_account(self, account_view):
        store = api.new_store()
        account = store.fetch(account_view.account)
        methods = PaymentMethod.get_by_account(store, account)
        if methods.count() > 0:
            if not yesno(
                    _('This account is used in at least one payment method.\n'
                      'To be able to delete it the payment methods needs to be'
                      're-configured first'), Gtk.ResponseType.NO,
                    _("Configure payment methods"), _("Keep account")):
                store.close()
                return
        elif not yesno(
                _('Are you sure you want to remove account "%s" ?') % (
                    (account_view.description, )), Gtk.ResponseType.NO,
                _("Remove account"), _("Keep account")):
            store.close()
            return

        if account_view.id in self._pages:
            account_page = self._pages[account_view.id]
            self._close_page(account_page)

        self.accounts.remove(account_view)
        self.accounts.flush()

        imbalance_id = api.sysparam.get_object_id('IMBALANCE_ACCOUNT')
        for method in methods:
            method.destination_account_id = imbalance_id

        account.remove(store)
        store.commit(close=True)

    def _delete_transaction(self, item):
        msg = _('Are you sure you want to remove transaction "%s" ?') % (
            (item.description))
        if not yesno(msg, Gtk.ResponseType.YES,
                     _(u"Remove transaction"), _(u"Keep transaction")):
            return

        account_transactions = self.get_current_page()
        account_transactions.result_view.remove(item)

        store = api.new_store()
        if isinstance(item.transaction, AccountTransactionView):
            account_transaction = store.fetch(item.transaction.transaction)
        else:
            account_transaction = store.fetch(item.transaction)
        account_transaction.delete(account_transaction.id, store=store)
        store.commit(close=True)
        account_transactions.update_totals()

    def _print_transaction_report(self):
        assert not self._is_accounts_tab()

        page = self.get_current_page()
        print_report(AccountTransactionReport, page.result_view,
                     list(page.result_view),
                     account=page.model,
                     filters=page.search.get_search_filters())

    #
    # Kiwi callbacks
    #

    def key_escape(self):
        if self._can_close_tab():
            self._close_current_page()
        return True

    def key_control_w(self):
        if self._can_close_tab():
            self._close_current_page()
        return True

    def on_accounts__row_activated(self, ktree, account_view):
        self._new_page(account_view)

    def on_accounts__selection_changed(self, ktree, account_view):
        self._update_actions()

    def on_accounts__right_click(self, results, result, event):
        self._popover.set_relative_to(results)
        self.show_popover(event)

    def on_Edit__activate(self, button):
        if self._is_accounts_tab():
            account_view = self.accounts.get_selected()
            self._edit_existing_account(account_view)
        elif self._is_transaction_tab():
            page = self.get_current_page()
            transaction = page.result_view.get_selected()
            page._edit_transaction_dialog(transaction)

    def after_notebook__switch_page(self, notebook, page, page_id):
        self._update_actions()
        self._update_tooltips()

    def _on_search__result_selection_changed(self, search):
        self._update_actions()

    def _on_search__result_item_popup_menu(self, search, objectlist, result, event):
        self._popover.set_relative_to(objectlist)
        self.show_popover(event)

    # Toolbar

    def on_NewAccount__activate(self, action):
        self._create_new_account()

    def on_NewTransaction__activate(self, action):
        self._add_transaction()

    def on_Delete__activate(self, action):
        if self._is_accounts_tab():
            account_view = self.accounts.get_selected()
            self._delete_account(account_view)
        elif self._is_transaction_tab():
            transactions = self.get_current_page()
            transaction = transactions.result_view.get_selected()
            self._delete_transaction(transaction)
            self.refresh_pages()
            self._refresh_accounts()

    # Financial

    def on_Import__activate(self, action):
        self._import()

    # Edit
    def on_ConfigurePaymentMethods__activate(self, action):
        from stoqlib.gui.dialogs.paymentmethod import PaymentMethodsDialog
        store = api.new_store()
        model = self.run_dialog(PaymentMethodsDialog, store)
        store.confirm(model)
        store.close()
Example #10
0
class AccountEditor(BaseEditor):
    """ Account Editor """
    gladefile = "AccountEditor"
    proxy_widgets = ['description', 'code']
    model_type = Account
    model_name = _('Account')

    def __init__(self, store, model=None, parent_account=None):
        self._last_account_type = None
        self._bank_number = -1
        self._bank_widgets = []
        self._bank_option_widgets = []
        self._option_fields = {}
        self._test_button = None
        self.existing = model is not None
        self.parent_account = parent_account
        self.bank_model = _TemporaryBankAccount()
        BaseEditor.__init__(self, store, model)

        action_area = self.main_dialog.action_area
        action_area.set_layout(gtk.BUTTONBOX_END)
        action_area.pack_start(self._test_button, expand=False, fill=False)
        action_area.set_child_secondary(self._test_button, True)
        self._test_button.show()

    #
    # BaseEditor hooks
    #

    def create_model(self, store):
        return Account(description=u"",
                       account_type=Account.TYPE_CASH,
                       store=store)

    def _setup_widgets(self):
        self._test_button = gtk.Button(_("Print a test bill"))
        self._test_button.connect('clicked',
                                  self._on_test_button__clicked)
        self.parent_accounts = AccountTree(with_code=False, create_mode=True)
        self.parent_accounts.connect('selection-changed',
                                     self._on_parent_accounts__selection_changed)
        self.tree_box.pack_start(self.parent_accounts)
        self.tree_box.reorder_child(self.parent_accounts, 0)

        if sysparam.compare_object('IMBALANCE_ACCOUNT', self.model):
            self.account_type.set_sensitive(False)

        self.account_type.prefill(Account.account_type_descriptions)
        account_type = self.model.account_type

        self.parent_accounts.insert_initial(self.store,
                                            edited_account=self.model)
        if self.parent_account:
            account = self.parent_accounts.get_account_by_id(
                self.parent_account.id)
            self.parent_accounts.select(account)
            if not self.existing:
                account_type = account.account_type
        self.account_type.select(account_type)
        self.parent_accounts.show()

    def setup_proxies(self):
        self._setup_widgets()
        self.add_proxy(self.model, AccountEditor.proxy_widgets)

    def validate_confirm(self):
        if not self.model.description:
            return False
        account = self.parent_accounts.get_selected()
        if not account:
            return True
        return account.selectable

    def on_confirm(self):
        new_parent = self.parent_accounts.get_selected()
        if new_parent:
            new_parent = new_parent.account
        if new_parent != self.model:
            self.model.parent = new_parent
        self.model.account_type = self.account_type.get_selected()
        self._save_bank()

    def refresh_ok(self, value):
        BaseEditor.refresh_ok(self, value)

        account_type = self.account_type.get_selected()
        if account_type != Account.TYPE_BANK:
            value = False
        self._test_button.set_sensitive(value)

    # Private

    def _save_bank(self):
        bank_account = self.model.bank
        if not bank_account:
            bank_account = BankAccount(account=self.model,
                                       store=self.store)
        # FIXME: Who sets this to a str?
        bank_account.bank_account = unicode(self.bank_model.bank_account)
        bank_account.bank_branch = unicode(self.bank_model.bank_branch)
        if self._bank_number is not None:
            bank_account.bank_number = self.bank_model.bank_number

        self._save_bank_bill_options(bank_account)

    def _save_bank_bill_options(self, bank_account):
        for option, entry in self._option_fields.items():
            value = unicode(entry.get_text())
            bill_option = self.store.find(BillOption,
                                          bank_account=bank_account,
                                          option=option).one()
            if bill_option is None:
                bill_option = BillOption(store=self.store,
                                         bank_account=bank_account,
                                         option=option,
                                         value=value)
            bill_option.value = value

    def _add_widget(self, label, widget, options=False):
        n_rows = self.table.props.n_rows
        l = gtk.Label()
        l.set_markup(label)
        l.props.xalign = 1.0
        self.table.resize(n_rows + 1, 2)
        self.table.attach(
            l, 0, 1, n_rows, n_rows + 1,
            gtk.FILL, 0, 0, 0)
        self.table.attach(
            widget, 1, 2, n_rows, n_rows + 1,
            gtk.EXPAND | gtk.FILL, 0, 0, 0)
        if options:
            self._bank_option_widgets.extend([l, widget])
        else:
            self._bank_widgets.extend([l, widget])
        l.show()
        return l

    def _update_bank_type(self):
        self._remove_bank_option_widgets()

        bank_number = self.bank_type.get_selected()
        bank_info = None
        if bank_number:
            bank_info = get_bank_info_by_number(bank_number)

        self.bank_number = ProxyEntry()
        self.bank_number.props.data_type = int
        self.bank_number.set_sensitive(False)
        bank_number_lbl = self._add_widget(api.escape(_("Number:")),
                                           self.bank_number, options=True)

        self.bank_branch = ProxyEntry()
        self.bank_branch.connect('validate', self._on_bank_branch__validate,
                                 bank_info)
        self.bank_branch.props.data_type = 'str'
        self.bank_branch.props.mandatory = True
        self.bank_branch.model_attribute = "bank_branch"
        bank_branch_lbl = self._add_widget(api.escape(_("Agency:")),
                                           self.bank_branch, options=True)
        if bank_number is not None:
            bank_branch_lbl.show()
            self.bank_branch.show()
        else:
            bank_branch_lbl.hide()

        self.bank_account = ProxyEntry()
        self.bank_account.connect('validate', self._on_bank_account__validate,
                                  bank_info)
        self._add_widget(api.escape(_("Account:")),
                         self.bank_account, options=True)
        self.bank_account.model_attribute = "bank_account"
        self.bank_account.props.data_type = 'str'
        if bank_number is not None:
            self.bank_account.props.mandatory = True
        self.bank_account.show()

        attributes = ['bank_account', 'bank_branch',
                      'bank_number']
        if bank_number is not None:
            bank_number_lbl.show()
            self.bank_number.show()

            self.bank_model.bank_number = bank_number

            for i, option in enumerate(bank_info.get_extra_options()):
                name = 'option' + str(i)
                entry = ProxyEntry()
                entry.model_attribute = name
                setattr(self, name, entry)
                # Set the model attr too so it can be validated
                setattr(self.bank_model, name, u'')
                entry.props.data_type = 'str'
                entry.connect('validate', self._on_bank_option__validate,
                              bank_info, option)
                self._add_widget("<i>%s</i>:" % (api.escape(option.capitalize()), ),
                                 entry, options=True)
                entry.show()
                self._option_fields[option] = entry
                attributes.append(entry.model_attribute)
        else:
            bank_number_lbl.hide()

        self.bank_proxy = self.add_proxy(
            self.bank_model, attributes)
        self._fill_bank_account()

    def _fill_bank_account(self):
        if not self.model.bank:
            return

        self.bank_model.bank_branch = self.model.bank.bank_branch.encode('utf-8')
        self.bank_model.bank_account = self.model.bank.bank_account.encode('utf-8')
        self.bank_proxy.update('bank_branch')
        self.bank_proxy.update('bank_account')

        bill_options = list(self.store.find(BillOption,
                                            bank_account=self.model.bank))
        for bill_option in bill_options:
            if bill_option.option is None:
                continue
            field_entry = self._option_fields.get(bill_option.option)
            if field_entry:
                field_entry.set_text(bill_option.value)

    def _update_account_type(self, account_type):
        if not self.account_type.get_sensitive():
            return
        if account_type != Account.TYPE_BANK:
            self._remove_bank_widgets()
            self._remove_bank_option_widgets()
            self.code.set_sensitive(True)
            return

        self.code.set_sensitive(False)
        self.bank_type = ProxyComboBox()
        self._add_widget(api.escape(_("Bank:")), self.bank_type)
        self.bank_type.connect('content-changed',
                               self._on_bank_type__content_changed)
        self.bank_type.show()

        banks = [(_('Generic'), None)]
        banks.extend([(b.description,
                       b.bank_number) for b in get_all_banks()])
        self.bank_type.prefill(banks)

        if self.model.bank:
            try:
                self.bank_type.select(self.model.bank.bank_number)
            except KeyError:
                self.bank_type.select(None)

        self._update_bank_type()

    def _remove_bank_widgets(self):
        for widget in self._bank_widgets:
            widget.get_parent().remove(widget)
            widget.destroy()
        self.table.resize(5, 2)
        self._bank_widgets = []

    def _remove_bank_option_widgets(self):
        for widget in self._bank_option_widgets:
            widget.get_parent().remove(widget)
            widget.destroy()
        self.table.resize(5 + len(self._bank_widgets) / 2, 2)
        self._bank_option_widgets = []
        self._option_fields = {}

    def _print_test_bill(self):
        try:
            bank_info = get_bank_info_by_number(self.bank_model.bank_number)
        except NotImplementedError:
            info(_("This bank does not support printing of bills"))
            return
        kwargs = dict(
            valor_documento=12345.67,
            data_vencimento=datetime.date.today(),
            data_documento=datetime.date.today(),
            data_processamento=datetime.date.today(),
            nosso_numero=u'624533',
            numero_documento=u'1138',
            sacado=[_(u"Drawee"), _(u"Address"), _(u"Details")],
            cedente=[_(u"Supplier"), _(u"Address"), _(u"Details"), ("CNPJ")],
            demonstrativo=[_(u"Demonstration")],
            instrucoes=[_(u"Instructions")],
            agencia=self.bank_model.bank_branch,
            conta=self.bank_model.bank_account,
        )
        for opt in self.bank_model.options:
            kwargs[opt.option] = opt.value
        data = bank_info(**kwargs)
        print_report(BillTestReport, data)

    # Callbacks

    def _on_parent_accounts__selection_changed(self, objectlist, account):
        self.force_validation()

    def on_description__activate(self, entry):
        if self.validate_confirm():
            self.confirm()

    def on_description__validate(self, entry, text):
        if not text:
            return ValidationError(_("Account description cannot be empty"))

    def on_account_type__content_changed(self, account_type):
        account_type = account_type.get_selected()
        if self._last_account_type == account_type:
            return
        self._update_account_type(account_type)
        self._last_account_type = account_type

    def _on_bank_type__content_changed(self, bank_type):
        bank_number = bank_type.get_selected()
        if self._bank_number == bank_number:
            return
        self._update_bank_type()

        self._bank_number = bank_number

    def _on_bank_branch__validate(self, entry, value, bank_info):
        if bank_info:
            try:
                bank_info.validate_field(value)
            except BoletoException as e:
                return ValidationError(str(e))

    def _on_bank_account__validate(self, entry, value, bank_info):
        if bank_info:
            try:
                bank_info.validate_field(value)
            except BoletoException as e:
                return ValidationError(str(e))

    def _on_bank_option__validate(self, entry, value, bank_info, option):
        try:
            bank_info.validate_option(option, value)
        except BoletoException as e:
            return ValidationError(str(e))
        self.bank_model.set_option(option, value)

    def _on_test_button__clicked(self, button):
        self._print_test_bill()