stock_account/models/analytic_account.py

112 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.tools import float_compare, float_is_zero, float_round
class AccountAnalyticPlan(models.Model):
_inherit = 'account.analytic.plan'
def _calculate_distribution_amount(self, amount, percentage, total_percentage, distribution_on_each_plan):
"""
Ensures that the total amount distributed across all lines always adds up to exactly `amount` per
plan. We try to correct for compounding rounding errors by assigning the exact outstanding amount when
we detect that a line will close out a plan's total percentage. However, since multiple plans can be
assigned to a line, with different prior distributions, there is the possible edge case that one line
closes out two (or more) tallies with different compounding errors. This means there is no one correct
amount that we can assign to a line that will correctly close out both all plans. This is described in
more detail in the commit message, under "concurrent closing line edge case".
"""
decimal_precision = self.env['decimal.precision'].precision_get('Percentage Analytic')
distributed_percentage, distributed_amount = distribution_on_each_plan.get(self, (0, 0))
allocated_percentage = distributed_percentage + percentage
if float_compare(allocated_percentage, total_percentage, precision_digits=decimal_precision) == 0:
calculated_amount = (amount * total_percentage / 100) - distributed_amount
else:
calculated_amount = amount * percentage / 100
distributed_amount += float_round(calculated_amount, precision_digits=decimal_precision)
distribution_on_each_plan[self] = (allocated_percentage, distributed_amount)
return calculated_amount
class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account'
def _perform_analytic_distribution(self, distribution, amount, unit_amount, lines, obj, additive=False):
"""
Redistributes the analytic lines to match the given distribution:
- For account_ids where lines already exist, the amount and unit_amount of these lines get updated,
lines where the updated amount becomes zero get unlinked.
- For account_ids where lines don't exist yet, the line values to create them are returned,
lines where the amount becomes zero are not included.
:param distribution: the desired distribution to match the analytic lines to
:param amount: the total amount to distribute over the analytic lines
:param unit_amount: the total unit amount (will not be distributed)
:param lines: the (current) analytic account lines that need to be matched to the new distribution
:param obj: the object on which _prepare_analytic_line_values(account_id, amount, unit_amount) will be
called to get the template for the values of new analytic line objects
:param additive: if True, the unit_amount and (distributed) amount get added to the existing lines
:returns: a list of dicts containing the values for new analytic lines that need to be created
:rtype: dict
"""
if not distribution:
lines.unlink()
return []
# Does this: {'15': 40, '14,16': 60} -> { account(15): 40, account(14,16): 60 }
distribution = {
self.env['account.analytic.account'].browse(map(int, ids.split(','))).exists(): percentage
for ids, percentage in distribution.items()
}
plans = self.env['account.analytic.plan']
plans = sum(plans._get_all_plans(), plans)
line_columns = [p._column_name() for p in plans]
lines_to_link = []
distribution_on_each_plan = {}
total_percentages = {}
for accounts, percentage in distribution.items():
for plan in accounts.root_plan_id:
total_percentages[plan] = total_percentages.get(plan, 0) + percentage
for existing_aal in lines:
# TODO: recommend something better for this line in review, please
accounts = sum(map(existing_aal.mapped, line_columns), self.env['account.analytic.account'])
if accounts in distribution:
# Update the existing AAL for this account
percentage = distribution[accounts]
new_amount = 0
new_unit_amount = unit_amount
for account in accounts:
plan = account.root_plan_id
new_amount = plan._calculate_distribution_amount(amount, percentage, total_percentages[plan], distribution_on_each_plan)
if additive:
new_amount += existing_aal.amount
new_unit_amount += existing_aal.unit_amount
currency = accounts[0].currency_id or obj.company_id.currency_id
if float_is_zero(new_amount, precision_rounding=currency.rounding):
existing_aal.unlink()
else:
existing_aal.amount = new_amount
existing_aal.unit_amount = new_unit_amount
# Prevent this distribution from being applied again
del distribution[accounts]
else:
# Delete the existing AAL if it is no longer present in the new distribution
existing_aal.unlink()
# Create new lines from remaining distributions
for accounts, percentage in distribution.items():
account_field_values = {}
for account in accounts:
new_amount = account.root_plan_id._calculate_distribution_amount(amount, percentage, total_percentages[plan], distribution_on_each_plan)
account_field_values[account.plan_id._column_name()] = account.id
currency = account.currency_id or obj.company_id.currency_id
if not float_is_zero(new_amount, precision_rounding=currency.rounding):
lines_to_link.append(obj._prepare_analytic_line_values(account_field_values, new_amount, unit_amount))
return lines_to_link