|
@ -67,8 +67,9 @@ class AccountingExpressionProcessor(object): |
|
|
r"(?P<accounts>_[a-zA-Z0-9]+|\[.*?\])" |
|
|
r"(?P<accounts>_[a-zA-Z0-9]+|\[.*?\])" |
|
|
r"(?P<domain>\[.*?\])?") |
|
|
r"(?P<domain>\[.*?\])?") |
|
|
|
|
|
|
|
|
def __init__(self, env): |
|
|
|
|
|
self.env = env |
|
|
|
|
|
|
|
|
def __init__(self, company): |
|
|
|
|
|
self.company = company |
|
|
|
|
|
self.dp = company.currency_id.decimal_places |
|
|
# before done_parsing: {(domain, mode): set(account_codes)} |
|
|
# before done_parsing: {(domain, mode): set(account_codes)} |
|
|
# after done_parsing: {(domain, mode): list(account_ids)} |
|
|
# after done_parsing: {(domain, mode): list(account_ids)} |
|
|
self._map_account_ids = defaultdict(set) |
|
|
self._map_account_ids = defaultdict(set) |
|
@ -83,8 +84,8 @@ class AccountingExpressionProcessor(object): |
|
|
# to get the variation, so it's a bit slower |
|
|
# to get the variation, so it's a bit slower |
|
|
self.smart_end = True |
|
|
self.smart_end = True |
|
|
|
|
|
|
|
|
def _load_account_codes(self, account_codes, company): |
|
|
|
|
|
account_model = self.env['account.account'] |
|
|
|
|
|
|
|
|
def _load_account_codes(self, account_codes): |
|
|
|
|
|
account_model = self.company.env['account.account'] |
|
|
exact_codes = set() |
|
|
exact_codes = set() |
|
|
for account_code in account_codes: |
|
|
for account_code in account_codes: |
|
|
if account_code in self._account_ids_by_code: |
|
|
if account_code in self._account_ids_by_code: |
|
@ -92,19 +93,19 @@ class AccountingExpressionProcessor(object): |
|
|
if account_code is None: |
|
|
if account_code is None: |
|
|
# None means we want all accounts |
|
|
# None means we want all accounts |
|
|
account_ids = account_model.\ |
|
|
account_ids = account_model.\ |
|
|
search([('company_id', '=', company.id)]).ids |
|
|
|
|
|
|
|
|
search([('company_id', '=', self.company.id)]).ids |
|
|
self._account_ids_by_code[account_code].update(account_ids) |
|
|
self._account_ids_by_code[account_code].update(account_ids) |
|
|
elif '%' in account_code: |
|
|
elif '%' in account_code: |
|
|
account_ids = account_model.\ |
|
|
account_ids = account_model.\ |
|
|
search([('code', '=like', account_code), |
|
|
search([('code', '=like', account_code), |
|
|
('company_id', '=', company.id)]).ids |
|
|
|
|
|
|
|
|
('company_id', '=', self.company.id)]).ids |
|
|
self._account_ids_by_code[account_code].update(account_ids) |
|
|
self._account_ids_by_code[account_code].update(account_ids) |
|
|
else: |
|
|
else: |
|
|
# search exact codes after the loop to do less queries |
|
|
# search exact codes after the loop to do less queries |
|
|
exact_codes.add(account_code) |
|
|
exact_codes.add(account_code) |
|
|
for account in account_model.\ |
|
|
for account in account_model.\ |
|
|
search([('code', 'in', list(exact_codes)), |
|
|
search([('code', 'in', list(exact_codes)), |
|
|
('company_id', '=', company.id)]): |
|
|
|
|
|
|
|
|
('company_id', '=', self.company.id)]): |
|
|
self._account_ids_by_code[account.code].add(account.id) |
|
|
self._account_ids_by_code[account.code].add(account.id) |
|
|
|
|
|
|
|
|
def _parse_match_object(self, mo): |
|
|
def _parse_match_object(self, mo): |
|
@ -146,13 +147,13 @@ class AccountingExpressionProcessor(object): |
|
|
key = (domain, mode) |
|
|
key = (domain, mode) |
|
|
self._map_account_ids[key].update(account_codes) |
|
|
self._map_account_ids[key].update(account_codes) |
|
|
|
|
|
|
|
|
def done_parsing(self, company): |
|
|
|
|
|
|
|
|
def done_parsing(self): |
|
|
"""Load account codes and replace account codes by |
|
|
"""Load account codes and replace account codes by |
|
|
account ids in map.""" |
|
|
account ids in map.""" |
|
|
for key, account_codes in self._map_account_ids.items(): |
|
|
for key, account_codes in self._map_account_ids.items(): |
|
|
# TODO _load_account_codes could be done |
|
|
# TODO _load_account_codes could be done |
|
|
# for all account_codes at once (also in v8) |
|
|
# for all account_codes at once (also in v8) |
|
|
self._load_account_codes(account_codes, company) |
|
|
|
|
|
|
|
|
self._load_account_codes(account_codes) |
|
|
account_ids = set() |
|
|
account_ids = set() |
|
|
for account_code in account_codes: |
|
|
for account_code in account_codes: |
|
|
account_ids.update(self._account_ids_by_code[account_code]) |
|
|
account_ids.update(self._account_ids_by_code[account_code]) |
|
@ -165,7 +166,7 @@ class AccountingExpressionProcessor(object): |
|
|
|
|
|
|
|
|
def get_aml_domain_for_expr(self, expr, |
|
|
def get_aml_domain_for_expr(self, expr, |
|
|
date_from, date_to, |
|
|
date_from, date_to, |
|
|
target_move, company, |
|
|
|
|
|
|
|
|
target_move, |
|
|
account_id=None): |
|
|
account_id=None): |
|
|
""" Get a domain on account.move.line for an expression. |
|
|
""" Get a domain on account.move.line for an expression. |
|
|
|
|
|
|
|
@ -197,15 +198,14 @@ class AccountingExpressionProcessor(object): |
|
|
if mode not in date_domain_by_mode: |
|
|
if mode not in date_domain_by_mode: |
|
|
date_domain_by_mode[mode] = \ |
|
|
date_domain_by_mode[mode] = \ |
|
|
self.get_aml_domain_for_dates(date_from, date_to, |
|
|
self.get_aml_domain_for_dates(date_from, date_to, |
|
|
mode, target_move, |
|
|
|
|
|
company) |
|
|
|
|
|
|
|
|
mode, target_move) |
|
|
assert aml_domains |
|
|
assert aml_domains |
|
|
return expression.OR(aml_domains) + \ |
|
|
return expression.OR(aml_domains) + \ |
|
|
expression.OR(date_domain_by_mode.values()) |
|
|
expression.OR(date_domain_by_mode.values()) |
|
|
|
|
|
|
|
|
def get_aml_domain_for_dates(self, date_from, date_to, |
|
|
def get_aml_domain_for_dates(self, date_from, date_to, |
|
|
mode, |
|
|
mode, |
|
|
target_move, company): |
|
|
|
|
|
|
|
|
target_move): |
|
|
if mode == self.MODE_VARIATION: |
|
|
if mode == self.MODE_VARIATION: |
|
|
domain = [('date', '>=', date_from), ('date', '<=', date_to)] |
|
|
domain = [('date', '>=', date_from), ('date', '<=', date_to)] |
|
|
elif mode in (self.MODE_INITIAL, self.MODE_END): |
|
|
elif mode in (self.MODE_INITIAL, self.MODE_END): |
|
@ -214,7 +214,8 @@ class AccountingExpressionProcessor(object): |
|
|
# sum from the beginning of time |
|
|
# sum from the beginning of time |
|
|
date_from_date = fields.Date.from_string(date_from) |
|
|
date_from_date = fields.Date.from_string(date_from) |
|
|
fy_date_from = \ |
|
|
fy_date_from = \ |
|
|
company.compute_fiscalyear_dates(date_from_date)['date_from'] |
|
|
|
|
|
|
|
|
self.company.\ |
|
|
|
|
|
compute_fiscalyear_dates(date_from_date)['date_from'] |
|
|
domain = ['|', |
|
|
domain = ['|', |
|
|
('date', '>=', fields.Date.to_string(fy_date_from)), |
|
|
('date', '>=', fields.Date.to_string(fy_date_from)), |
|
|
('user_type_id.include_initial_balance', '=', True)] |
|
|
('user_type_id.include_initial_balance', '=', True)] |
|
@ -225,21 +226,22 @@ class AccountingExpressionProcessor(object): |
|
|
elif mode == self.MODE_UNALLOCATED: |
|
|
elif mode == self.MODE_UNALLOCATED: |
|
|
date_from_date = fields.Date.from_string(date_from) |
|
|
date_from_date = fields.Date.from_string(date_from) |
|
|
fy_date_from = \ |
|
|
fy_date_from = \ |
|
|
company.compute_fiscalyear_dates(date_from_date)['date_from'] |
|
|
|
|
|
|
|
|
self.company.\ |
|
|
|
|
|
compute_fiscalyear_dates(date_from_date)['date_from'] |
|
|
domain = [('date', '<', fields.Date.to_string(fy_date_from)), |
|
|
domain = [('date', '<', fields.Date.to_string(fy_date_from)), |
|
|
('user_type_id.include_initial_balance', '=', False)] |
|
|
('user_type_id.include_initial_balance', '=', False)] |
|
|
if target_move == 'posted': |
|
|
if target_move == 'posted': |
|
|
domain.append(('move_id.state', '=', 'posted')) |
|
|
domain.append(('move_id.state', '=', 'posted')) |
|
|
return expression.normalize_domain(domain) |
|
|
return expression.normalize_domain(domain) |
|
|
|
|
|
|
|
|
def do_queries(self, company, date_from, date_to, |
|
|
|
|
|
|
|
|
def do_queries(self, date_from, date_to, |
|
|
target_move='posted', additional_move_line_filter=None): |
|
|
target_move='posted', additional_move_line_filter=None): |
|
|
"""Query sums of debit and credit for all accounts and domains |
|
|
"""Query sums of debit and credit for all accounts and domains |
|
|
used in expressions. |
|
|
used in expressions. |
|
|
|
|
|
|
|
|
This method must be executed after done_parsing(). |
|
|
This method must be executed after done_parsing(). |
|
|
""" |
|
|
""" |
|
|
aml_model = self.env['account.move.line'] |
|
|
|
|
|
|
|
|
aml_model = self.company.env['account.move.line'] |
|
|
# {(domain, mode): {account_id: (debit, credit)}} |
|
|
# {(domain, mode): {account_id: (debit, credit)}} |
|
|
self._data = defaultdict(dict) |
|
|
self._data = defaultdict(dict) |
|
|
domain_by_mode = {} |
|
|
domain_by_mode = {} |
|
@ -253,7 +255,7 @@ class AccountingExpressionProcessor(object): |
|
|
if mode not in domain_by_mode: |
|
|
if mode not in domain_by_mode: |
|
|
domain_by_mode[mode] = \ |
|
|
domain_by_mode[mode] = \ |
|
|
self.get_aml_domain_for_dates(date_from, date_to, |
|
|
self.get_aml_domain_for_dates(date_from, date_to, |
|
|
mode, target_move, company) |
|
|
|
|
|
|
|
|
mode, target_move) |
|
|
domain = list(domain) + domain_by_mode[mode] |
|
|
domain = list(domain) + domain_by_mode[mode] |
|
|
domain.append(('account_id', 'in', self._map_account_ids[key])) |
|
|
domain.append(('account_id', 'in', self._map_account_ids[key])) |
|
|
if additional_move_line_filter: |
|
|
if additional_move_line_filter: |
|
@ -266,7 +268,8 @@ class AccountingExpressionProcessor(object): |
|
|
debit = acc['debit'] or 0.0 |
|
|
debit = acc['debit'] or 0.0 |
|
|
credit = acc['credit'] or 0.0 |
|
|
credit = acc['credit'] or 0.0 |
|
|
if mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and \ |
|
|
if mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and \ |
|
|
float_is_zero(debit-credit, precision_rounding=4): |
|
|
|
|
|
|
|
|
float_is_zero(debit-credit, |
|
|
|
|
|
precision_rounding=self.dp): |
|
|
# in initial mode, ignore accounts with 0 balance |
|
|
# in initial mode, ignore accounts with 0 balance |
|
|
continue |
|
|
continue |
|
|
self._data[key][acc['account_id'][0]] = (debit, credit) |
|
|
self._data[key][acc['account_id'][0]] = (debit, credit) |
|
@ -311,7 +314,7 @@ class AccountingExpressionProcessor(object): |
|
|
# as it does not make sense to distinguish 0 from "no data" |
|
|
# as it does not make sense to distinguish 0 from "no data" |
|
|
if v is not AccountingNone and \ |
|
|
if v is not AccountingNone and \ |
|
|
mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and \ |
|
|
mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and \ |
|
|
float_is_zero(v, precision_rounding=4): |
|
|
|
|
|
|
|
|
float_is_zero(v, precision_rounding=self.dp): |
|
|
v = AccountingNone |
|
|
v = AccountingNone |
|
|
return '(' + repr(v) + ')' |
|
|
return '(' + repr(v) + ')' |
|
|
|
|
|
|
|
@ -342,7 +345,7 @@ class AccountingExpressionProcessor(object): |
|
|
# as it does not make sense to distinguish 0 from "no data" |
|
|
# as it does not make sense to distinguish 0 from "no data" |
|
|
if v is not AccountingNone and \ |
|
|
if v is not AccountingNone and \ |
|
|
mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and \ |
|
|
mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and \ |
|
|
float_is_zero(v, precision_rounding=4): |
|
|
|
|
|
|
|
|
float_is_zero(v, precision_rounding=self.dp): |
|
|
v = AccountingNone |
|
|
v = AccountingNone |
|
|
return '(' + repr(v) + ')' |
|
|
return '(' + repr(v) + ')' |
|
|
|
|
|
|
|
@ -365,13 +368,13 @@ class AccountingExpressionProcessor(object): |
|
|
def _get_balances(cls, mode, company, date_from, date_to, |
|
|
def _get_balances(cls, mode, company, date_from, date_to, |
|
|
target_move='posted'): |
|
|
target_move='posted'): |
|
|
expr = 'deb{mode}[], crd{mode}[]'.format(mode=mode) |
|
|
expr = 'deb{mode}[], crd{mode}[]'.format(mode=mode) |
|
|
aep = AccountingExpressionProcessor(company.env) |
|
|
|
|
|
|
|
|
aep = AccountingExpressionProcessor(company) |
|
|
# disable smart_end to have the data at once, instead |
|
|
# disable smart_end to have the data at once, instead |
|
|
# of initial + variation |
|
|
# of initial + variation |
|
|
aep.smart_end = False |
|
|
aep.smart_end = False |
|
|
aep.parse_expr(expr) |
|
|
aep.parse_expr(expr) |
|
|
aep.done_parsing(company) |
|
|
|
|
|
aep.do_queries(company, date_from, date_to, target_move) |
|
|
|
|
|
|
|
|
aep.done_parsing() |
|
|
|
|
|
aep.do_queries(date_from, date_to, target_move) |
|
|
return aep._data[((), mode)] |
|
|
return aep._data[((), mode)] |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
@ -395,7 +398,7 @@ class AccountingExpressionProcessor(object): |
|
|
""" A convenience method to obtain the ending balances of all accounts |
|
|
""" A convenience method to obtain the ending balances of all accounts |
|
|
at a given date. |
|
|
at a given date. |
|
|
|
|
|
|
|
|
It is the same as get_balances_init(date+1). |
|
|
|
|
|
|
|
|
It is the same as get_balances_initial(date+1). |
|
|
|
|
|
|
|
|
:param company: |
|
|
:param company: |
|
|
:param date: |
|
|
:param date: |
|
|