# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from decimal import Decimal from sql import Literal, Null from sql.aggregate import Sum from sql.conditionals import Coalesce from trytond import backend from trytond.i18n import gettext from trytond.model import fields from trytond.model.exceptions import AccessError from trytond.modules.currency.fields import Monetary from trytond.pool import Pool, PoolMeta from trytond.tools import grouped_slice, reduce_ids, sqlite_apply_types from trytond.transaction import Transaction class Company(metaclass=PoolMeta): __name__ = 'company.company' receivable = fields.Function(Monetary( "Receivable", currency='currency', digits='currency'), 'get_receivable_payable', searcher='search_receivable_payable') payable = fields.Function(Monetary( "Payable", currency='currency', digits='currency'), 'get_receivable_payable', searcher='search_receivable_payable') receivable_today = fields.Function(Monetary( "Receivable Today", currency='currency', digits='currency'), 'get_receivable_payable', searcher='search_receivable_payable') payable_today = fields.Function(Monetary( "Payable Today", currency='currency', digits='currency'), 'get_receivable_payable', searcher='search_receivable_payable') @classmethod def get_receivable_payable(cls, companies, names): amounts = {} pool = Pool() MoveLine = pool.get('account.move.line') Account = pool.get('account.account') AccountType = pool.get('account.account.type') Date = pool.get('ir.date') cursor = Transaction().connection.cursor() line = MoveLine.__table__() account = Account.__table__() account_type = AccountType.__table__() for name in names: assert name in { 'receivable', 'payable', 'receivable_today', 'payable_today'} amounts[name] = dict.fromkeys(map(int, companies), Decimal(0)) exp = { c.id: Decimal(str(10.0 ** -c.currency.digits)) for c in companies} today = Date.today() amount = Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)) for name in names: if name.endswith('_today'): code = name[:-len('_today')] today_where = ( (line.maturity_date <= today) | (line.maturity_date == Null)) else: code = name today_where = Literal(True) for sub_companies in grouped_slice(companies): sub_ids = [p.id for p in sub_companies] company_where = reduce_ids(account.company, sub_ids) query = (line .join(account, condition=account.id == line.account) .join(account_type, condition=account.type == account_type.id) .select(account.company.as_('id'), amount.as_(name), where=(getattr(account_type, code) & (line.reconciliation == Null) & company_where & today_where), group_by=account.company)) if backend.name == 'sqlite': sqlite_apply_types(query, [None, 'NUMERIC']) cursor.execute(*query) for company_id, value in cursor: amounts[name][company_id] = value.quantize(exp[company_id]) return amounts @classmethod def search_receivable_payable(cls, name, clause): pool = Pool() MoveLine = pool.get('account.move.line') Account = pool.get('account.account') AccountType = pool.get('account.account.type') Date = pool.get('ir.date') line = MoveLine.__table__() account = Account.__table__() account_type = AccountType.__table__() assert name in { 'receivable', 'payable', 'receivable_today', 'payable_today'} _, operator, value = clause today = Date.today() if name.endswith('_today'): code = name[:-len('_today')] today_query = ((line.maturity_date <= today) | (line.maturity_date == Null)) else: code = name today_query = Literal(True) Operator = fields.SQL_OPERATORS[operator] # Need to cast numeric for sqlite cast_ = MoveLine.debit.sql_cast amount = cast_(Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0))) if operator in {'in', 'not in'}: value = [cast_(Literal(Decimal(v or 0))) for v in value] else: value = cast_(Literal(Decimal(value or 0))) query = (line .join(account, condition=account.id == line.account) .join(account_type, condition=account.type == account_type.id) .select(account.company, where=(getattr(account_type, code) & (line.reconciliation == Null) & today_query), group_by=account.company, having=Operator(amount, value))) return [('id', 'in', query)] @classmethod def check_modification(cls, mode, companies, values=None, external=False): pool = Pool() Move = pool.get('account.move') super().check_modification( mode, companies, values=values, external=external) if mode == 'write': if 'currency' in values: moves = Move.search([ ('company', 'in', [c.id for c in companies]), ], limit=1, order=[]) if moves: raise AccessError(gettext( 'account.msg_company_change_currency'))