def add_filter_by_attribute(self, attr, title, data_type, valid_values=None, callback=None, use_having=False, multiple_selection=False): """Add a filter accordingly to the attributes specified :param attr: attribute that will be filtered. This can be either the name of the attribute or the attribute itself. :param title: the title of the filter that will be visible in the interface :param data_type: the attribute type (str, bool, decimal, etc) :param callback: the callback function that will be triggered :param use_having: use having expression in the query """ if data_type is not bool: title += ':' if data_type == datetime.date: filter = DateSearchFilter(title) if valid_values: filter.clear_options() filter.add_custom_options() for opt in valid_values: filter.add_option(opt) filter.select(valid_values[0]) elif (data_type == decimal.Decimal or data_type == int or data_type == currency): filter = NumberSearchFilter(title) if data_type != int: filter.set_digits(2) elif data_type == str: if multiple_selection: filter = MultiSearchFilter(title, valid_values) elif valid_values: filter = ComboSearchFilter(title, valid_values) filter.enable_advanced() else: filter = StringSearchFilter(title) filter.enable_advanced() elif data_type == bool: filter = BoolSearchFilter(title) else: raise NotImplementedError(title, data_type) filter.set_removable() self.add_filter(filter, columns=[attr], callback=callback, use_having=use_having) if data_type is not bool: label = filter.get_title_label() label.set_alignment(1.0, 0.5) self.label_group.add_widget(label) combo = filter.get_mode_combo() if combo: self.combo_group.add_widget(combo) return filter
class DateRangeDialog(BasicDialog): """A simple dialog for selecting a date range When confirmed, a :class:`date_range` object will be returned containig the information about the date range selected """ title = _(u'Select a date range') size = (-1, -1) def __init__(self, title=None, header_text=None): title = title or self.title header_text = '<b>%s</b>' % header_text if header_text else '' BasicDialog.__init__(self, title=title, header_text=header_text) self._setup_widgets() # # BasicDialog # def confirm(self): BasicDialog.confirm(self) state = self.date_filter.get_state() if isinstance(state, DateQueryState): start, end = state.date, state.date else: start, end = state.start, state.end self.retval = date_range(start=start, end=end) # # Private # def _setup_widgets(self): self.date_filter = DateSearchFilter(_(u'Date:')) # FIXME: add a remove_option method in DateSearchFilter. self.date_filter.clear_options() self.date_filter.add_custom_options() for option in [Today, Yesterday, LastWeek, LastMonth]: self.date_filter.add_option(option) self.date_filter.select(position=0) self.vbox.pack_start(self.date_filter, False, False, 0) self.date_filter.show_all()
class TillDailyMovementDialog(BaseEditor): """Shows informations related to till operations over a daterange. It can also be filtered by branch. """ title = _("Daily Movement") hide_footer = True size = (950, 450) model_type = Settable gladefile = "TillDailyMovementDialog" proxy_widgets = [ 'branch', 'in_subtotal', 'in_credit', 'in_total', 'out_subtotal', 'out_credit', 'out_total' ] # # Private # def _setup_widgets(self): # Branches combo items = stoq_api.get_branches_for_filter(self.store) self.branch.prefill(items) # Daterange filter self.date_filter = DateSearchFilter(_(u'Date:')) self.date_filter.clear_options() self.date_filter.add_custom_options() for option in [Today, Yesterday, LastWeek, LastMonth]: self.date_filter.add_option(option) self.date_filter.select(position=0) self.daterange_hbox.pack_start(self.date_filter, False, False, 0) self.date_filter.show_all() # Setting report lists' columns self.sales_list.set_columns(self._get_sales_columns()) self.inpayments_list.set_columns(self._get_lonely_payments_columns()) self.purchases_list.set_columns(self._get_purchases_columns()) self.outpayments_list.set_columns(self._get_lonely_payments_columns()) self.return_sales_list.set_columns(self._get_return_sales_columns()) self.supplies_list.set_columns(self._get_till_columns()) self.removals_list.set_columns(self._get_till_columns()) self.permethod_list.set_columns(self._get_permethod_columns()) self.percard_list.set_columns(self._get_percard_columns()) # Print button is insensitive, until the first report is generated self.print_button.set_sensitive(False) self._setup_summary_labels() def _get_sales_columns(self): return [ IdentifierColumn('identifier', title=_('Sale #'), sorted=True), Column('salesperson', title=_('Sales Person'), data_type=str), Column('client', title=_('Client'), data_type=str, expand=True), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Value'), data_type=str, justify=Gtk.Justification.RIGHT) ] def _get_lonely_payments_columns(self): return [ IdentifierColumn('identifier', title=_('Payment #'), sorted=True), Column('method', title=_('Method'), data_type=str), Column('description', title=_('Description'), expand=True, data_type=str), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Payment Value'), data_type=currency) ] def _get_purchases_columns(self): return [ IdentifierColumn('identifier', title=_('Code #'), sorted=True), Column('status_str', title=_('Status'), data_type=str), Column('responsible_name', title=_('Responsible'), expand=True, data_type=str), Column('branch_name', title=_('Branch'), data_type=str), Column('notes', title=_('Notes'), data_type=str), Column('supplier_name', title=_('Supplier'), data_type=str), Column('purchase_total', title=_('Value'), data_type=currency) ] def _get_return_sales_columns(self): return [ IdentifierColumn('identifier', title=_('Code #'), sorted=True), Column('salesperson', title=_('Sales Person'), data_type=str), Column('client', title=_('Client'), expand=True, data_type=str), Column('return_date', title=_('Return Date'), data_type=datetime.date), Column('branch', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Sale Value'), data_type=currency) ] def _get_till_columns(self): return [ IdentifierColumn('identifier', title=_('Entry #'), sorted=True), Column('description', title=_('Description'), data_type=str, expand=True), Column('branch_name', title=_('Branch'), data_type=str, visible=False), Column('value', title=_('Value'), data_type=currency) ] def _get_permethod_columns(self): return [ Column('method', title=_('Payment Method'), sorted=True, expand=True), Column('in_value', title=_('Income Total'), data_type=currency), Column('out_value', title=_('Outgoing Total'), data_type=currency) ] def _get_percard_columns(self): return [ Column('provider', title=_('Provider Name'), data_type=str, expand=True), Column('income', title=_('Income Total'), data_type=currency) ] def _create_summary_label(self, report, column='value', label=None): # Setting tha data obj_list = getattr(self, report + '_list') box = getattr(self, report + '_vbox') if label is None: label = _('Total:') label = '<b>%s</b>' % stoq_api.escape(label) value_format = '<b>%s</b>' # Creating the label label = SummaryLabel(klist=obj_list, column=column, label=label, value_format=value_format) # Displaying the label box.pack_start(label, False, False, 0) label.show() return label def _setup_summary_labels(self): # Supplies self.supplies_label = self._create_summary_label('supplies') # Removals self.removals_label = self._create_summary_label('removals') # Percard self.percard_label = self._create_summary_label('percard', column='income') def _update_summary_labels(self): self.supplies_label.update_total() self.removals_label.update_total() self.percard_label.update_total() self.proxy.update_many(('in_subtotal', 'in_credit', 'in_total', 'out_subtotal', 'out_credit', 'out_total')) def _generate_dailymovement_data(self, store): query = And( Payment.status.is_in([Payment.STATUS_PENDING, Payment.STATUS_PAID]), self._get_query(Payment.open_date, Payment.branch)) # Keys are the sale objects, and values are lists with all payments self.sales = collections.OrderedDict() # Keys are the returned sale objects, and values are lists with all payments self.return_sales = collections.OrderedDict() self.purchases = collections.OrderedDict() # lonely input and output payments self.lonely_in_payments = [] self.lonely_out_payments = [] # values are lists with the first element the summary of the input, and # the second the summary of the output method_summary = {} self.card_summary = {} result = store.find(DailyInPaymentView, query) for p in result.order_by(Sale.identifier, Payment.identifier): if p.sale: subtotal = p.sale_subtotal total = p.sale.get_total_sale_amount(subtotal) salesperson = p.salesperson_name or _('Not Specified') client = p.client_name or _('Not Specified') sale = DailyMovementSale(identifier=p.sale.identifier, salesperson=salesperson, client=client, branch=p.branch_name, value=get_formatted_price(total)) sale_payments = self.sales.setdefault( sale, collections.OrderedDict()) details = '' method_desc = p.method.get_description() if p.check_data: account = p.check_data.bank_account numbers = sorted(payment.payment_number for payment in p.sale.payments if bool(payment.payment_number)) # Ensure that the check numbers are ordered parts = [] if account.bank_number: parts.append(_(u'Bank: %s') % account.bank_number) if account.bank_branch: parts.append(_(u'Agency: %s') % account.bank_branch) if account.bank_account: parts.append(_(u'Account: %s') % account.bank_account) if numbers: parts.append(_(u'Numbers: %s') % ', '.join(numbers)) details = ' / '.join(parts) if p.card_data: if p.card_data.card_type == CreditCardData.TYPE_DEBIT: method_desc += ' ' + _('Debit') else: method_desc += ' ' + _(u'Credit') details = '%s - %s - %s' % ( p.card_data.auth, p.card_data.provider.short_name or '', p.card_data.device.description or '') key = (method_desc, details) item = sale_payments.setdefault(key, [0, 0]) item[0] += p.value item[1] += 1 else: self.lonely_in_payments.append(p) method_summary.setdefault(p.method, [0, 0]) method_summary[p.method][0] += p.value if p.card_data: type_desc = p.card_data.short_desc[p.card_data.card_type] key = (p.card_data.provider.short_name, type_desc) self.card_summary.setdefault(key, 0) self.card_summary[key] += p.value result = store.find(DailyOutPaymentView, query) for p in result.order_by(Payment.identifier): if p.purchase: purchase_payments = self.purchases.setdefault(p.purchase, []) purchase_payments.append(p) elif p.sale: subtotal = p.sale_subtotal value = p.sale.get_total_sale_amount(subtotal) salesperson = p.salesperson_name or _('Not Specified') client = p.client_name or _('Not Specified') sale = DailyMovementSale(identifier=p.sale.identifier, salesperson=salesperson, client=client, return_date=p.sale.return_date, branch=p.branch_name, value=value) return_sales_payment = self.return_sales.setdefault(sale, []) return_sales_payment.append(p) else: self.lonely_out_payments.append(p) method_summary.setdefault(p.method, [0, 0]) method_summary[p.method][1] += p.value self.method_summary = [] for method, (in_value, out_value) in method_summary.items(): self.method_summary.append((method, in_value, out_value)) self.method_summary.sort(key=lambda m: _(m[0].description)) # Till removals query = And(Eq(TillEntry.payment_id, None), self._get_query(TillEntry.date, TillEntry.branch), TillEntry.value < 0) self.till_removals = store.find(TillEntry, query) # Till supply query = And(Eq(TillEntry.payment_id, None), self._get_query(TillEntry.date, TillEntry.branch), TillEntry.value > 0) self.till_supplies = store.find(TillEntry, query) def _show_lonely_payments(self, payments, widget): widget.clear() for payment in payments: payment_data = Settable(identifier=payment.identifier, method=payment.method.get_description(), description=payment.description, branch=payment.branch_name, value=payment.value) widget.append(payment_data) def _show_report(self): self._generate_dailymovement_data(self.store) # Sale data self.sales_list.clear() for sale, payments in self.sales.items(): self.sales_list.append(None, sale) for details, values in payments.items(): value = '%s (%sx)' % (get_formatted_price( values[0]), values[1]) payment_data = Settable(identifier=None, salesperson=details[0], client=details[1], value=value) self.sales_list.append(sale, payment_data) # Lonely in payments self._show_lonely_payments(self.lonely_in_payments, self.inpayments_list) # Purchase data self.purchases_list.clear() for purchase, payments in self.purchases.items(): self.purchases_list.append(None, purchase) for payment in payments: # TODO Add details refering to Bank, Agency later payment_data = Settable(identifier=payment.identifier, notes=payment.method.get_description()) self.purchases_list.append(purchase, payment_data) # Lonely out payments self._show_lonely_payments(self.lonely_out_payments, self.outpayments_list) # Return sales self.return_sales_list.clear() for sale, payments in self.return_sales.items(): self.return_sales_list.append(None, sale) for payment in payments: payment_data = Settable( identifier=payment.identifier, salesperson=payment.method.get_description(), client=payment.description, value=get_formatted_price(payment.value)) self.return_sales_list.append(sale, payment_data) # Supplies self.supplies_list.clear() self.supplies_list.add_list(self.till_supplies) # Removals self.removals_list.clear() self.removals_list.add_list(self.till_removals) # Summary's per payment method data self.permethod_list.clear() self.model.in_subtotal = self.model.out_subtotal = 0 self.model.in_credit = self.model.out_credit = currency(0) for method in self.method_summary: method_data = Settable(method=_(method[0].description), in_value=method[1], out_value=method[2]) self.permethod_list.append(method_data) self.model.in_subtotal += method[1] self.model.out_subtotal += method[2] if method[0].method_name == 'credit': self.model.in_credit = currency(method[1]) self.model.out_credit = currency(method[2]) self.model.in_subtotal = currency(self.model.in_subtotal) self.model.out_subtotal = currency(self.model.out_subtotal) self.model.in_total = currency(self.model.in_subtotal - self.model.in_credit) self.model.out_total = currency(self.model.out_subtotal - self.model.out_credit) # Summary's per card provider data self.percard_list.clear() keys = list(self.card_summary.keys()) for key in sorted(keys): card_summary_data = Settable(provider=key[0] + ' ' + key[1], income=self.card_summary[key]) self.percard_list.append(card_summary_data) self._update_summary_labels() def _get_query(self, date_attr, branch_attr): daterange = self.get_daterange() query = [ Date(date_attr) >= Date(daterange[0]), Date(date_attr) <= Date(daterange[1]) ] branch = self.model.branch if branch is not None: query.append(branch_attr == branch) return And(*query) # # Public API # def get_daterange(self): start = self.date_filter.get_start_date() end = self.date_filter.get_end_date() return (start, end) def set_daterange(self, start, end=None): self.date_filter.set_state(start, end) # # BaseEditor Hooks # def create_model(self, store): return Settable(branch=api.get_current_branch(store), in_total=currency(0), in_credit=currency(0), in_subtotal=currency(0), out_total=currency(0), out_credit=currency(0), out_subtotal=currency(0)) def setup_proxies(self): self._setup_widgets() self.proxy = self.add_proxy(self.model, TillDailyMovementDialog.proxy_widgets) # # Callbacks # def on_search_button__clicked(self, widget): self._show_report() self.print_button.set_sensitive(True) def on_print_button__clicked(self, widget): branch = self.model.branch daterange = self.get_daterange() print_report(TillDailyMovementReport, self.store, branch, daterange, self)
class TransactionPage(object): # shows either a list of: # - transactions # - payments def __init__(self, model, app, parent): self.model = model self.app = app self.parent_window = parent self._block = False self._create_search() self._add_date_filter() self._setup_search() self.refresh() def get_toplevel(self): return self.parent_window def _create_search(self): self.search = SearchSlave(self._get_columns(self.model.kind), store=self.app.store) self.search.connect('result-item-activated', self._on_search__item_activated) self.search.enable_advanced_search() self.search.set_result_view(FinancialSearchResults) self.result_view = self.search.result_view self.result_view.page = self self.result_view.set_cell_data_func( self._on_result_view__cell_data_func) tree_view = self.search.result_view.get_treeview() tree_view.set_rules_hint(True) tree_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH) def _add_date_filter(self): self.date_filter = DateSearchFilter(_('Date:')) self.date_filter.clear_options() self.date_filter.add_option(Any, 0) year = datetime.datetime.today().year month_names = get_month_names() for i, month in enumerate(month_names): name = month_names[i] option = type(name + 'Option', (MonthOption, ), { 'name': _(name), 'month': i + 1, 'year': year }) self.date_filter.add_option(option, i + 1) self.date_filter.add_custom_options() self.date_filter.select(Any) self.search.add_filter(self.date_filter) def _append_date_query(self, field): date = self.date_filter.get_state() queries = [] if isinstance(date, DateQueryState) and date.date is not None: queries.append(Date(field) == date.date) elif isinstance(date, DateIntervalQueryState): queries.append(Date(field) >= date.start) queries.append(Date(field) <= date.end) return queries def _payment_query(self, store): executer = self.search.get_query_executer() search_spec = executer.search_spec queries = self._append_date_query(search_spec.due_date) if queries: return store.find(search_spec, And(*queries)) return store.find(search_spec) def _transaction_query(self, store): queries = [ Or(self.model.id == AccountTransaction.account_id, self.model.id == AccountTransaction.source_account_id) ] queries.extend(self._append_date_query(AccountTransaction.date)) return store.find(AccountTransactionView, And(*queries)) def show(self): self.search.show() def _setup_search(self): if self.model.kind == 'account': self.search.set_search_spec(AccountTransactionView) self.search.set_text_field_columns(['description']) self.search.set_query(self._transaction_query) elif self.model.kind == 'payable': self.search.set_text_field_columns( ['description', 'supplier_name']) self.search.set_search_spec(OutPaymentView) self.search.set_query(self._payment_query) elif self.model.kind == 'receivable': self.search.set_text_field_columns(['description', 'drawee']) self.search.set_search_spec(InPaymentView) self.search.set_query(self._payment_query) else: raise TypeError("unknown model kind: %r" % (self.model.kind, )) def refresh(self): self.search.refresh() def _get_columns(self, kind): if kind in ['payable', 'receivable']: return self._get_payment_columns() else: return self._get_account_columns() def _on_result_view__cell_data_func(self, column, renderer, account_view, text): if not isinstance(renderer, Gtk.CellRendererText): return text if self.model.kind != 'account': return text trans = account_view.transaction is_imbalance = self.app._imbalance_account_id in [ trans.dest_account_id, trans.source_account_id ] renderer.set_property('weight-set', is_imbalance) if is_imbalance: renderer.set_property('weight', Pango.Weight.BOLD) return text def _get_account_columns(self): def format_withdrawal(value): if value < 0: return currency(abs(value)).format(symbol=True, precision=2) def format_deposit(value): if value > 0: return currency(value).format(symbol=True, precision=2) if self.model.account_type == Account.TYPE_INCOME: color_func = lambda x: False else: color_func = lambda x: x < 0 return [ Column('date', title=_("Date"), data_type=datetime.date, sorted=True), Column('code', title=_("Code"), data_type=str), Column('description', title=_("Description"), data_type=str, expand=True), Column('account', title=_("Account"), data_type=str), Column('value', title=self.model.account.get_type_label(out=False), data_type=currency, format_func=format_deposit), Column('value', title=self.model.account.get_type_label(out=True), data_type=currency, format_func=format_withdrawal), ColoredColumn('total', title=_("Total"), data_type=currency, color='red', data_func=color_func) ] def _get_payment_columns(self): return [ SearchColumn('due_date', title=_("Due date"), data_type=datetime.date, sorted=True), IdentifierColumn('identifier', title=_("Payment #")), SearchColumn('description', title=_("Description"), data_type=str, expand=True), SearchColumn('value', title=_("Value"), data_type=currency) ] def append_transactions(self, transactions): for transaction in transactions: description = transaction.get_account_description(self.model) value = transaction.get_value(self.model) # If a transaction has the source equals to the destination account. # Show the same transaction, but with reversed value. if transaction.source_account_id == transaction.dest_account_id: self._add_transaction(transaction, description, -value) self._add_transaction(transaction, description, value) self.update_totals() def _add_transaction(self, transaction, description, value): item = Settable(transaction=transaction) self._update_transaction(item, transaction, description, value) self.search.result_view.append(item) return item def _update_transaction(self, item, transaction, description, value): item.account = description item.date = transaction.date item.description = transaction.description item.value = value item.code = transaction.code def update_totals(self): total = decimal.Decimal('0') for item in self.search.result_view: total += item.value item.total = total def _edit_transaction_dialog(self, item): store = api.new_store() if isinstance(item.transaction, AccountTransactionView): account_transaction = store.fetch(item.transaction.transaction) else: account_transaction = store.fetch(item.transaction) model = getattr(self.model, 'account', self.model) transaction = run_dialog(AccountTransactionEditor, self.app, store, account_transaction, model) store.confirm(transaction) if transaction: self.app.refresh_pages() self.update_totals() self.app.accounts.refresh_accounts(self.app.store) store.close() def on_dialog__opened(self, dialog): dialog.connect('account-added', self.on_dialog__account_added) def on_dialog__account_added(self, dialog): self.app.accounts.refresh_accounts(self.app.store) def add_transaction_dialog(self): store = api.new_store() model = getattr(self.model, 'account', self.model) model = store.fetch(model) transaction = run_dialog(AccountTransactionEditor, self.app, store, None, model) store.confirm(transaction) if transaction: self.app.refresh_pages() self.update_totals() self.app.accounts.refresh_accounts(self.app.store) store.close() def _on_search__item_activated(self, objectlist, item): if self.model.kind == 'account': self._edit_transaction_dialog(item)