216 lines
12 KiB
Python
216 lines
12 KiB
Python
from collections import defaultdict
|
|
from itertools import product
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.fields import Domain
|
|
from odoo.tools.sql import column_exists, create_column
|
|
|
|
|
|
class AccountMoveLine(models.Model):
|
|
_inherit = 'account.move.line'
|
|
|
|
esg_emission_factor_id = fields.Many2one('esg.emission.factor', string='Emission Factor', compute='_compute_esg_emission_factor_id', inverse='_inverse_esg_emission_factor_id', store=True, readonly=False, index='btree_not_null')
|
|
esg_uncertainty_value = fields.Float(string='Uncertainty (%)', related='esg_emission_factor_id.esg_uncertainty_value')
|
|
esg_uncertainty_absolute_value = fields.Float(string='Uncertainty (kgCO₂e)', compute='_compute_esg_uncertainty_absolute_value')
|
|
esg_emission_multiplicator = fields.Float(compute='_compute_esg_emission_multiplicator', store=True, export_string_translation=False) # Technical field, storing the multiplicator to apply to the gas volumes (important for the report)
|
|
esg_emissions_value = fields.Float(string='Emissions (kgCO₂e)', compute='_compute_esg_emissions_value')
|
|
|
|
def _auto_init(self):
|
|
if not column_exists(self.env.cr, "account_move_line", "esg_emission_factor_id"):
|
|
# Create the column to avoid computation during installation
|
|
create_column(self.env.cr, "account_move_line", "esg_emission_factor_id", "int4")
|
|
create_column(self.env.cr, "account_move_line", "esg_emission_multiplicator", "float8")
|
|
return super()._auto_init()
|
|
|
|
@api.depends(
|
|
'quantity', 'price_subtotal', 'esg_emission_factor_id.compute_method', 'esg_emission_factor_id.currency_id',
|
|
'esg_emission_factor_id.uom_id', 'product_uom_id', 'currency_id', 'move_type', 'account_type',
|
|
)
|
|
def _compute_esg_emission_multiplicator(self):
|
|
for line in self:
|
|
if line.account_type not in self.env['account.account'].ESG_VALID_ACCOUNT_TYPES or not line.esg_emission_factor_id:
|
|
line.esg_emission_multiplicator = 0
|
|
elif line.esg_emission_factor_id.compute_method == 'monetary':
|
|
aml_currency = line.currency_id or line.company_id.currency_id
|
|
if aml_currency and line.esg_emission_factor_id.currency_id:
|
|
line.esg_emission_multiplicator = aml_currency._convert(
|
|
from_amount=line.price_subtotal,
|
|
to_currency=line.esg_emission_factor_id.currency_id,
|
|
date=line.date,
|
|
round=False,
|
|
)
|
|
else:
|
|
line.esg_emission_multiplicator = 0
|
|
else:
|
|
aml_uom = line.product_uom_id or line.esg_emission_factor_id.uom_id
|
|
if aml_uom and line.esg_emission_factor_id.uom_id:
|
|
line.esg_emission_multiplicator = aml_uom._compute_quantity(
|
|
qty=line.quantity,
|
|
to_unit=line.esg_emission_factor_id.uom_id,
|
|
round=False,
|
|
)
|
|
else:
|
|
line.esg_emission_multiplicator = 0
|
|
|
|
if line.move_type == 'in_refund':
|
|
line.esg_emission_multiplicator *= -1
|
|
|
|
@api.depends('esg_emission_multiplicator', 'esg_emission_factor_id.esg_emissions_value')
|
|
def _compute_esg_emissions_value(self):
|
|
for line in self:
|
|
if line.account_type not in self.env['account.account'].ESG_VALID_ACCOUNT_TYPES:
|
|
line.esg_emissions_value = 0
|
|
else:
|
|
line.esg_emissions_value = line.esg_emission_factor_id.esg_emissions_value * line.esg_emission_multiplicator
|
|
|
|
@api.depends('esg_emissions_value', 'esg_uncertainty_value')
|
|
def _compute_esg_uncertainty_absolute_value(self):
|
|
for line in self:
|
|
if line.account_type not in self.env['account.account'].ESG_VALID_ACCOUNT_TYPES:
|
|
line.esg_uncertainty_absolute_value = 0
|
|
else:
|
|
line.esg_uncertainty_absolute_value = line.esg_emissions_value * line.esg_uncertainty_value
|
|
|
|
@api.depends('product_id', 'account_id', 'partner_id')
|
|
def _compute_esg_emission_factor_id(self):
|
|
move_lines = self.filtered(lambda aml: aml.account_type in self.env['account.account'].ESG_VALID_ACCOUNT_TYPES)
|
|
if move_lines:
|
|
move_lines.with_context(auto_generate_esg_assignation_rule=False)._assign_factors_to_move_lines(no_match_reset=True)
|
|
|
|
def _inverse_esg_emission_factor_id(self):
|
|
if not (
|
|
self.env.context.get('auto_generate_esg_assignation_rule', True)
|
|
and (move_lines_with_emission_factor := self.filtered('esg_emission_factor_id'))
|
|
):
|
|
return
|
|
assignation_vals_list = []
|
|
esg_emission_factor_id_assignation_key = {}
|
|
# domain to search a rule containing the same combinaison (product_id, partner_id, account_id) linked
|
|
# to another emission_factor to remove it since the user breaks it
|
|
assignation_rules_to_remove_domain = Domain.FALSE
|
|
# domain to search the existing assignation rules to avoid creating the same rule
|
|
assignation_rules_to_check_domain = Domain.FALSE
|
|
lines_per_group = defaultdict(self.env['account.move.line'].browse)
|
|
for line in move_lines_with_emission_factor:
|
|
for combinaison in product(*[(v, False) for v in (line.product_id.id, line.partner_id.id, line.account_id.id)]):
|
|
lines_per_group[combinaison] |= line
|
|
|
|
expected_nb_matches_per_group = {
|
|
(product_id, partner_id, account_id): 1 + [product_id, partner_id, account_id].count(False)
|
|
for product_id in [True, False]
|
|
for partner_id in [True, False]
|
|
for account_id in [True, False]
|
|
if product_id or partner_id or account_id
|
|
}
|
|
|
|
processed_account_move_line_ids = set()
|
|
for (product_bool, partner_bool, account_bool), expected_nb_matches in expected_nb_matches_per_group.items():
|
|
for (product_id, partner_id, account_id), lines in lines_per_group.items():
|
|
if (
|
|
product_bool != bool(product_id)
|
|
or partner_bool != bool(partner_id)
|
|
or account_bool != bool(account_id)
|
|
or all(line.id in processed_account_move_line_ids for line in lines)
|
|
):
|
|
continue
|
|
esg_emission_factor = lines.esg_emission_factor_id
|
|
combination_domain = Domain([
|
|
('product_id', '=', product_id),
|
|
('partner_id', '=', partner_id),
|
|
('account_id', '=', account_id),
|
|
])
|
|
if len(esg_emission_factor) > 1:
|
|
# break assignation rules (to remove if exists)
|
|
assignation_rules_to_remove_domain |= combination_domain
|
|
continue
|
|
assignation_rules_to_remove_domain |= combination_domain & Domain([
|
|
('esg_emission_factor_id', '!=', esg_emission_factor.id),
|
|
])
|
|
if len(lines) >= expected_nb_matches + 1:
|
|
processed_account_move_line_ids.update(lines.ids)
|
|
esg_emission_factor_id_assignation_key[product_id, partner_id, account_id] = esg_emission_factor.id
|
|
assignation_rules_to_check_domain |= combination_domain
|
|
continue
|
|
domain = Domain([('esg_emission_factor_id', '!=', False), ('id', 'not in', lines.ids)])
|
|
if product_id:
|
|
domain &= Domain([('product_id', '=', product_id)])
|
|
if partner_id:
|
|
domain &= Domain([('partner_id', '=', partner_id)])
|
|
if account_id:
|
|
domain &= Domain([('account_id', '=', account_id)])
|
|
other_move_lines = self.env['account.move.line'].search(domain, limit=10)
|
|
nb_matches = len(lines) - 1
|
|
for other_line in other_move_lines:
|
|
if other_line.esg_emission_factor_id == esg_emission_factor:
|
|
nb_matches += 1
|
|
if nb_matches >= expected_nb_matches:
|
|
processed_account_move_line_ids.update(lines.ids)
|
|
esg_emission_factor_id_assignation_key[product_id, partner_id, account_id] = esg_emission_factor.id
|
|
assignation_rules_to_check_domain |= combination_domain
|
|
break
|
|
if not assignation_rules_to_check_domain.is_false():
|
|
assignation_rule_read_group = self.env['esg.assignation.line']._read_group(
|
|
assignation_rules_to_check_domain,
|
|
['product_id', 'partner_id', 'account_id'],
|
|
['esg_emission_factor_id:recordset']
|
|
)
|
|
esg_emission_factor_per_rule = {
|
|
(product.id, partner.id, account.id): emission_factors
|
|
for product, partner, account, emission_factors in assignation_rule_read_group
|
|
}
|
|
for (product_id, partner_id, account_id), esg_emission_factor_id in esg_emission_factor_id_assignation_key.items():
|
|
esg_emission_factor = esg_emission_factor_per_rule.get((product_id, partner_id, account_id), self.env['esg.emission.factor'])
|
|
if not esg_emission_factor:
|
|
assignation_vals_list.append({
|
|
'product_id': product_id,
|
|
'partner_id': partner_id,
|
|
'account_id': account_id,
|
|
'esg_emission_factor_id': esg_emission_factor_id,
|
|
})
|
|
assignation_rules_to_remove_domain |= Domain([
|
|
('product_id', '=', product_id),
|
|
('partner_id', '=', partner_id),
|
|
('account_id', '=', account_id),
|
|
('esg_emission_factor_id', '!=', esg_emission_factor_id),
|
|
])
|
|
elif esg_emission_factor.id != esg_emission_factor_id:
|
|
# broken rule, should be removed
|
|
assignation_rules_to_remove_domain |= Domain([('product_id', '=', product_id), ('partner_id', '=', partner_id), ('account_id', '=', account_id)])
|
|
if not assignation_rules_to_remove_domain.is_false():
|
|
assignation_rules_to_remove = self.env['esg.assignation.line'].search(assignation_rules_to_remove_domain)
|
|
if assignation_rules_to_remove:
|
|
assignation_rules_to_remove.unlink()
|
|
if assignation_vals_list:
|
|
self.env['esg.assignation.line'].create(assignation_vals_list)
|
|
|
|
def _assign_factors_to_move_lines(self, factors=None, no_match_reset=False):
|
|
domain = [
|
|
'|', '|',
|
|
('product_id', 'in', self.product_id.ids),
|
|
('partner_id', 'in', self.partner_id.ids),
|
|
('account_id', 'in', self.account_id.ids),
|
|
]
|
|
if factors:
|
|
domain = Domain.AND([domain, [('esg_emission_factor_id', 'in', factors.ids)]])
|
|
factor_per_rule = {
|
|
(rule.product_id.id, rule.partner_id.id, rule.account_id.id): rule.esg_emission_factor_id.id
|
|
for rule in self.env['esg.assignation.line'].search(domain)
|
|
}
|
|
|
|
modified_aml_ids = []
|
|
for line in self:
|
|
initial_rule = (line.product_id.id, line.partner_id.id, line.account_id.id)
|
|
combinations = list(product(*[(v, False) for v in initial_rule]))
|
|
assigned = False
|
|
for rule in combinations:
|
|
if rule in factor_per_rule:
|
|
line.esg_emission_factor_id = factor_per_rule[rule]
|
|
modified_aml_ids.append(line.id)
|
|
assigned = True
|
|
break
|
|
if not assigned and (no_match_reset or line.esg_emission_factor_id in factors):
|
|
line.esg_emission_factor_id = False
|
|
modified_aml_ids.append(line.id)
|
|
|
|
return tuple(modified_aml_ids)
|