mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-22 11:12:00 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import analytic_plan
|
||||
from . import analytic_account
|
||||
from . import analytic_line
|
||||
from . import analytic_mixin
|
||||
from . import analytic_distribution_model
|
||||
from . import res_config_settings
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,191 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountAnalyticAccount(models.Model):
|
||||
_name = 'account.analytic.account'
|
||||
_inherit = ['mail.thread']
|
||||
_description = 'Analytic Account'
|
||||
_order = 'plan_id, name asc'
|
||||
_check_company_auto = True
|
||||
_rec_names_search = ['name', 'code']
|
||||
|
||||
name = fields.Char(
|
||||
string='Analytic Account',
|
||||
index='trigram',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
code = fields.Char(
|
||||
string='Reference',
|
||||
index='btree',
|
||||
tracking=True,
|
||||
)
|
||||
active = fields.Boolean(
|
||||
'Active',
|
||||
help="Deactivate the account.",
|
||||
default=True,
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
plan_id = fields.Many2one(
|
||||
'account.analytic.plan',
|
||||
string='Plan',
|
||||
check_company=True,
|
||||
required=True,
|
||||
)
|
||||
root_plan_id = fields.Many2one(
|
||||
'account.analytic.plan',
|
||||
string='Root Plan',
|
||||
check_company=True,
|
||||
compute="_compute_root_plan",
|
||||
store=True,
|
||||
)
|
||||
color = fields.Integer(
|
||||
'Color Index',
|
||||
related='plan_id.color',
|
||||
)
|
||||
|
||||
line_ids = fields.One2many(
|
||||
'account.analytic.line',
|
||||
'account_id',
|
||||
string="Analytic Lines",
|
||||
)
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
# use auto_join to speed up name_search call
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Customer',
|
||||
auto_join=True,
|
||||
tracking=True,
|
||||
check_company=True,
|
||||
)
|
||||
|
||||
balance = fields.Monetary(
|
||||
compute='_compute_debit_credit_balance',
|
||||
string='Balance',
|
||||
)
|
||||
debit = fields.Monetary(
|
||||
compute='_compute_debit_credit_balance',
|
||||
string='Debit',
|
||||
)
|
||||
credit = fields.Monetary(
|
||||
compute='_compute_debit_credit_balance',
|
||||
string='Credit',
|
||||
)
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
related="company_id.currency_id",
|
||||
string="Currency",
|
||||
)
|
||||
|
||||
@api.constrains('company_id')
|
||||
def _check_company_consistency(self):
|
||||
analytic_accounts = self.filtered('company_id')
|
||||
|
||||
if not analytic_accounts:
|
||||
return
|
||||
|
||||
self.flush_recordset(['company_id'])
|
||||
self.env['account.analytic.line'].flush_model(['account_id', 'company_id'])
|
||||
|
||||
self._cr.execute('''
|
||||
SELECT line.account_id
|
||||
FROM account_analytic_line line
|
||||
JOIN account_analytic_account account ON line.account_id = account.id
|
||||
WHERE line.company_id != account.company_id and account.company_id IS NOT NULL
|
||||
AND account.id IN %s
|
||||
''', [tuple(self.ids)])
|
||||
|
||||
if self._cr.fetchone():
|
||||
raise UserError(_("You can't set a different company on your analytic account since there are some analytic items linked to it."))
|
||||
|
||||
def name_get(self):
|
||||
res = []
|
||||
for analytic in self:
|
||||
name = analytic.name
|
||||
if analytic.code:
|
||||
name = f'[{analytic.code}] {name}'
|
||||
if analytic.partner_id.commercial_partner_id.name:
|
||||
name = f'{name} - {analytic.partner_id.commercial_partner_id.name}'
|
||||
res.append((analytic.id, name))
|
||||
return res
|
||||
|
||||
def copy_data(self, default=None):
|
||||
default = dict(default or {})
|
||||
default.setdefault('name', _("%s (copy)", self.name))
|
||||
return super().copy_data(default)
|
||||
|
||||
@api.model
|
||||
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||
"""
|
||||
Override read_group to calculate the sum of the non-stored fields that depend on the user context
|
||||
"""
|
||||
res = super(AccountAnalyticAccount, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
|
||||
accounts = self.env['account.analytic.account']
|
||||
for line in res:
|
||||
if '__domain' in line:
|
||||
accounts = self.search(line['__domain'])
|
||||
if 'balance' in fields:
|
||||
line['balance'] = sum(accounts.mapped('balance'))
|
||||
if 'debit' in fields:
|
||||
line['debit'] = sum(accounts.mapped('debit'))
|
||||
if 'credit' in fields:
|
||||
line['credit'] = sum(accounts.mapped('credit'))
|
||||
return res
|
||||
|
||||
@api.depends('line_ids.amount')
|
||||
def _compute_debit_credit_balance(self):
|
||||
Curr = self.env['res.currency']
|
||||
analytic_line_obj = self.env['account.analytic.line']
|
||||
domain = [
|
||||
('account_id', 'in', self.ids),
|
||||
('company_id', 'in', [False] + self.env.companies.ids)
|
||||
]
|
||||
if self._context.get('from_date', False):
|
||||
domain.append(('date', '>=', self._context['from_date']))
|
||||
if self._context.get('to_date', False):
|
||||
domain.append(('date', '<=', self._context['to_date']))
|
||||
|
||||
user_currency = self.env.company.currency_id
|
||||
credit_groups = analytic_line_obj.read_group(
|
||||
domain=domain + [('amount', '>=', 0.0)],
|
||||
fields=['account_id', 'currency_id', 'amount'],
|
||||
groupby=['account_id', 'currency_id'],
|
||||
lazy=False,
|
||||
)
|
||||
data_credit = defaultdict(float)
|
||||
for l in credit_groups:
|
||||
data_credit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
|
||||
l['amount'], user_currency, self.env.company, fields.Date.today())
|
||||
|
||||
debit_groups = analytic_line_obj.read_group(
|
||||
domain=domain + [('amount', '<', 0.0)],
|
||||
fields=['account_id', 'currency_id', 'amount'],
|
||||
groupby=['account_id', 'currency_id'],
|
||||
lazy=False,
|
||||
)
|
||||
data_debit = defaultdict(float)
|
||||
for l in debit_groups:
|
||||
data_debit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
|
||||
l['amount'], user_currency, self.env.company, fields.Date.today())
|
||||
|
||||
for account in self:
|
||||
account.debit = abs(data_debit.get(account.id, 0.0))
|
||||
account.credit = data_credit.get(account.id, 0.0)
|
||||
account.balance = account.credit - account.debit
|
||||
|
||||
@api.depends('plan_id', 'plan_id.parent_path')
|
||||
def _compute_root_plan(self):
|
||||
for account in self:
|
||||
account.root_plan_id = int(account.plan_id.parent_path[:-1].split('/')[0]) if account.plan_id.parent_path else None
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class NonMatchingDistribution(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AccountAnalyticDistributionModel(models.Model):
|
||||
_name = 'account.analytic.distribution.model'
|
||||
_inherit = 'analytic.mixin'
|
||||
_description = 'Analytic Distribution Model'
|
||||
_rec_name = 'create_date'
|
||||
_order = 'id desc'
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Partner',
|
||||
ondelete='cascade',
|
||||
help="Select a partner for which the analytic distribution will be used (e.g. create new customer invoice or Sales order if we select this partner, it will automatically take this as an analytic account)",
|
||||
)
|
||||
partner_category_id = fields.Many2one(
|
||||
'res.partner.category',
|
||||
string='Partner Category',
|
||||
ondelete='cascade',
|
||||
help="Select a partner category for which the analytic distribution will be used (e.g. create new customer invoice or Sales order if we select this partner, it will automatically take this as an analytic account)",
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
ondelete='cascade',
|
||||
help="Select a company for which the analytic distribution will be used (e.g. create new customer invoice or Sales order if we select this company, it will automatically take this as an analytic account)",
|
||||
)
|
||||
|
||||
@api.constrains('company_id')
|
||||
def _check_company_accounts(self):
|
||||
query = """
|
||||
SELECT model.id
|
||||
FROM account_analytic_distribution_model model
|
||||
JOIN account_analytic_account account
|
||||
ON model.analytic_distribution ? CAST(account.id AS VARCHAR)
|
||||
WHERE account.company_id IS NOT NULL
|
||||
AND (model.company_id IS NULL
|
||||
OR model.company_id != account.company_id)
|
||||
"""
|
||||
self.flush_model(['company_id', 'analytic_distribution'])
|
||||
self.env.cr.execute(query)
|
||||
if self.env.cr.dictfetchone():
|
||||
raise UserError(_('You defined a distribution with analytic account(s) belonging to a specific company but a model shared between companies or with a different company'))
|
||||
|
||||
@api.model
|
||||
def _get_distribution(self, vals):
|
||||
""" Returns the distribution model that has the most fields that corresponds to the vals given
|
||||
This method should be called to prefill analytic distribution field on several models """
|
||||
domain = []
|
||||
for fname, value in vals.items():
|
||||
domain += self._create_domain(fname, value) or []
|
||||
best_score = 0
|
||||
res = {}
|
||||
fnames = set(self._get_fields_to_check())
|
||||
for rec in self.search(domain):
|
||||
try:
|
||||
score = sum(rec._check_score(key, vals.get(key)) for key in fnames)
|
||||
if score > best_score:
|
||||
res = rec.analytic_distribution
|
||||
best_score = score
|
||||
except NonMatchingDistribution:
|
||||
continue
|
||||
return res
|
||||
|
||||
def _get_fields_to_check(self):
|
||||
return (
|
||||
{field.name for field in self._fields.values() if not field.manual}
|
||||
- set(self.env['analytic.mixin']._fields)
|
||||
- set(models.MAGIC_COLUMNS) - {'display_name', '__last_update'}
|
||||
)
|
||||
|
||||
def _check_score(self, key, value):
|
||||
self.ensure_one()
|
||||
if key == 'company_id':
|
||||
if not self.company_id or value == self.company_id.id:
|
||||
return 1 if self.company_id else 0.5
|
||||
raise NonMatchingDistribution
|
||||
if not self[key]:
|
||||
return 0
|
||||
if value and ((self[key].id in value) if isinstance(value, (list, tuple))
|
||||
else (value.startswith(self[key])) if key.endswith('_prefix')
|
||||
else (value == self[key].id)
|
||||
):
|
||||
return 1
|
||||
raise NonMatchingDistribution
|
||||
|
||||
def _create_domain(self, fname, value):
|
||||
if not value:
|
||||
return False
|
||||
if fname == 'partner_category_id':
|
||||
value += [False]
|
||||
return [(fname, 'in', value)]
|
||||
else:
|
||||
return [(fname, 'in', [value, False])]
|
||||
|
||||
def action_read_distribution_model(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': self.display_name,
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'account.analytic.distribution.model',
|
||||
'res_id': self.id,
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_name = 'account.analytic.line'
|
||||
_description = 'Analytic Line'
|
||||
_order = 'date desc, id desc'
|
||||
_check_company_auto = True
|
||||
|
||||
name = fields.Char(
|
||||
'Description',
|
||||
required=True,
|
||||
)
|
||||
date = fields.Date(
|
||||
'Date',
|
||||
required=True,
|
||||
index=True,
|
||||
default=fields.Date.context_today,
|
||||
)
|
||||
amount = fields.Monetary(
|
||||
'Amount',
|
||||
required=True,
|
||||
default=0.0,
|
||||
)
|
||||
unit_amount = fields.Float(
|
||||
'Quantity',
|
||||
default=0.0,
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
'uom.uom',
|
||||
string='Unit of Measure',
|
||||
domain="[('category_id', '=', product_uom_category_id)]",
|
||||
)
|
||||
product_uom_category_id = fields.Many2one(
|
||||
related='product_uom_id.category_id',
|
||||
string='UoM Category',
|
||||
readonly=True,
|
||||
)
|
||||
account_id = fields.Many2one(
|
||||
'account.analytic.account',
|
||||
'Analytic Account',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
index=True,
|
||||
check_company=True,
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Partner',
|
||||
check_company=True,
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
'res.users',
|
||||
string='User',
|
||||
default=lambda self: self.env.context.get('user_id', self.env.user.id),
|
||||
index=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
required=True,
|
||||
readonly=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
currency_id = fields.Many2one(
|
||||
related="company_id.currency_id",
|
||||
string="Currency",
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute_sudo=True,
|
||||
)
|
||||
plan_id = fields.Many2one(
|
||||
'account.analytic.plan',
|
||||
related='account_id.plan_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
compute_sudo=True,
|
||||
)
|
||||
category = fields.Selection(
|
||||
[('other', 'Other')],
|
||||
default='other',
|
||||
)
|
||||
|
||||
@api.constrains('company_id', 'account_id')
|
||||
def _check_company_id(self):
|
||||
for line in self:
|
||||
if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id:
|
||||
raise ValidationError(_('The selected account belongs to another company than the one you\'re trying to create an analytic item for'))
|
||||
119
odoo-bringout-oca-ocb-analytic/analytic/models/analytic_mixin.py
Normal file
119
odoo-bringout-oca-ocb-analytic/analytic/models/analytic_mixin.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.tools.float_utils import float_round, float_compare
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
class AnalyticMixin(models.AbstractModel):
|
||||
_name = 'analytic.mixin'
|
||||
_description = 'Analytic Mixin'
|
||||
|
||||
analytic_distribution = fields.Json(
|
||||
'Analytic',
|
||||
compute="_compute_analytic_distribution", store=True, copy=True, readonly=False,
|
||||
)
|
||||
# Json non stored to be able to search on analytic_distribution.
|
||||
analytic_distribution_search = fields.Json(
|
||||
store=False,
|
||||
search="_search_analytic_distribution"
|
||||
)
|
||||
analytic_precision = fields.Integer(
|
||||
store=False,
|
||||
default=lambda self: self.env['decimal.precision'].precision_get("Percentage Analytic"),
|
||||
)
|
||||
|
||||
def init(self):
|
||||
# Add a gin index for json search on the keys, on the models that actually have a table
|
||||
query = ''' SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_name=%s '''
|
||||
self.env.cr.execute(query, [self._table])
|
||||
if self.env.cr.dictfetchone():
|
||||
query = f"""
|
||||
CREATE INDEX IF NOT EXISTS {self._table}_analytic_distribution_gin_index
|
||||
ON {self._table} USING gin(analytic_distribution);
|
||||
"""
|
||||
self.env.cr.execute(query)
|
||||
super().init()
|
||||
|
||||
@api.model
|
||||
def fields_get(self, allfields=None, attributes=None):
|
||||
""" Hide analytic_distribution_search from filterable/searchable fields"""
|
||||
res = super().fields_get(allfields, attributes)
|
||||
if res.get('analytic_distribution_search'):
|
||||
res['analytic_distribution_search']['searchable'] = False
|
||||
return res
|
||||
|
||||
def _compute_analytic_distribution(self):
|
||||
pass
|
||||
|
||||
def _search_analytic_distribution(self, operator, value):
|
||||
if operator == 'in' and isinstance(value, (tuple, list)):
|
||||
account_ids = value
|
||||
operator_inselect = 'inselect'
|
||||
elif operator in ('=', '!=', 'ilike', 'not ilike') and isinstance(value, (str, bool)):
|
||||
operator_name_search = '=' if operator in ('=', '!=') else 'ilike'
|
||||
account_ids = list(self.env['account.analytic.account']._name_search(name=value, operator=operator_name_search))
|
||||
operator_inselect = 'inselect' if operator in ('=', 'ilike') else 'not inselect'
|
||||
else:
|
||||
raise UserError(_('Operation not supported'))
|
||||
|
||||
query = f"""
|
||||
SELECT id
|
||||
FROM {self._table}
|
||||
WHERE analytic_distribution ?| array[%s]
|
||||
"""
|
||||
return [('id', operator_inselect, (query, [[str(account_id) for account_id in account_ids]]))]
|
||||
|
||||
@api.model
|
||||
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
|
||||
args = self._apply_analytic_distribution_domain(args)
|
||||
return super()._search(args, offset, limit, order, count, access_rights_uid)
|
||||
|
||||
@api.model
|
||||
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||
domain = self._apply_analytic_distribution_domain(domain)
|
||||
return super().read_group(domain, fields, groupby, offset, limit, orderby, lazy)
|
||||
|
||||
def write(self, vals):
|
||||
""" Format the analytic_distribution float value, so equality on analytic_distribution can be done """
|
||||
decimal_precision = self.env['decimal.precision'].precision_get('Percentage Analytic')
|
||||
vals = self._sanitize_values(vals, decimal_precision)
|
||||
return super().write(vals)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
""" Format the analytic_distribution float value, so equality on analytic_distribution can be done """
|
||||
decimal_precision = self.env['decimal.precision'].precision_get('Percentage Analytic')
|
||||
vals_list = [self._sanitize_values(vals, decimal_precision) for vals in vals_list]
|
||||
return super().create(vals_list)
|
||||
|
||||
def _validate_distribution(self, **kwargs):
|
||||
if self.env.context.get('validate_analytic', False):
|
||||
mandatory_plans_ids = [plan['id'] for plan in self.env['account.analytic.plan'].sudo().get_relevant_plans(**kwargs) if plan['applicability'] == 'mandatory']
|
||||
if not mandatory_plans_ids:
|
||||
return
|
||||
decimal_precision = self.env['decimal.precision'].precision_get('Percentage Analytic')
|
||||
distribution_by_root_plan = {}
|
||||
for analytic_account_id, percentage in (self.analytic_distribution or {}).items():
|
||||
root_plan = self.env['account.analytic.account'].browse(int(analytic_account_id)).root_plan_id
|
||||
distribution_by_root_plan[root_plan.id] = distribution_by_root_plan.get(root_plan.id, 0) + percentage
|
||||
|
||||
for plan_id in mandatory_plans_ids:
|
||||
if float_compare(distribution_by_root_plan.get(plan_id, 0), 100, precision_digits=decimal_precision) != 0:
|
||||
raise ValidationError(_("One or more lines require a 100% analytic distribution."))
|
||||
|
||||
def _sanitize_values(self, vals, decimal_precision):
|
||||
""" Normalize the float of the distribution """
|
||||
if 'analytic_distribution' in vals:
|
||||
vals['analytic_distribution'] = vals.get('analytic_distribution') and {
|
||||
account_id: float_round(distribution, decimal_precision) for account_id, distribution in vals['analytic_distribution'].items()}
|
||||
return vals
|
||||
|
||||
def _apply_analytic_distribution_domain(self, domain):
|
||||
return [
|
||||
('analytic_distribution_search', leaf[1], leaf[2])
|
||||
if len(leaf) == 3 and leaf[0] == 'analytic_distribution' and isinstance(leaf[2], (str, tuple, list))
|
||||
else leaf
|
||||
for leaf in domain
|
||||
]
|
||||
219
odoo-bringout-oca-ocb-analytic/analytic/models/analytic_plan.py
Normal file
219
odoo-bringout-oca-ocb-analytic/analytic/models/analytic_plan.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from random import randint
|
||||
|
||||
|
||||
class AccountAnalyticPlan(models.Model):
|
||||
_name = 'account.analytic.plan'
|
||||
_description = 'Analytic Plans'
|
||||
_parent_store = True
|
||||
_rec_name = 'complete_name'
|
||||
_order = 'complete_name asc'
|
||||
_check_company_auto = True
|
||||
|
||||
def _default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char(required=True)
|
||||
description = fields.Text(string='Description')
|
||||
parent_id = fields.Many2one(
|
||||
'account.analytic.plan',
|
||||
string="Parent",
|
||||
ondelete='cascade',
|
||||
domain="[('id', '!=', id), ('company_id', 'in', [False, company_id])]",
|
||||
check_company=True,
|
||||
)
|
||||
parent_path = fields.Char(
|
||||
index='btree',
|
||||
unaccent=False,
|
||||
)
|
||||
children_ids = fields.One2many(
|
||||
'account.analytic.plan',
|
||||
'parent_id',
|
||||
string="Childrens",
|
||||
)
|
||||
children_count = fields.Integer(
|
||||
'Children Plans Count',
|
||||
compute='_compute_children_count',
|
||||
)
|
||||
complete_name = fields.Char(
|
||||
'Complete Name',
|
||||
compute='_compute_complete_name',
|
||||
recursive=True,
|
||||
store=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
account_ids = fields.One2many(
|
||||
'account.analytic.account',
|
||||
'plan_id',
|
||||
string="Accounts",
|
||||
)
|
||||
account_count = fields.Integer(
|
||||
'Analytic Accounts Count',
|
||||
compute='_compute_analytic_account_count',
|
||||
)
|
||||
all_account_count = fields.Integer(
|
||||
'All Analytic Accounts Count',
|
||||
compute='_compute_all_analytic_account_count',
|
||||
)
|
||||
color = fields.Integer(
|
||||
'Color',
|
||||
default=_default_color,
|
||||
)
|
||||
|
||||
default_applicability = fields.Selection(
|
||||
selection=[
|
||||
('optional', 'Optional'),
|
||||
('mandatory', 'Mandatory'),
|
||||
('unavailable', 'Unavailable'),
|
||||
],
|
||||
string="Default Applicability",
|
||||
required=True,
|
||||
default='optional',
|
||||
readonly=False,
|
||||
)
|
||||
applicability_ids = fields.One2many(
|
||||
'account.analytic.applicability',
|
||||
'analytic_plan_id',
|
||||
string='Applicability',
|
||||
)
|
||||
|
||||
@api.depends('name', 'parent_id.complete_name')
|
||||
def _compute_complete_name(self):
|
||||
for plan in self:
|
||||
if plan.parent_id:
|
||||
plan.complete_name = '%s / %s' % (plan.parent_id.complete_name, plan.name)
|
||||
else:
|
||||
plan.complete_name = plan.name
|
||||
|
||||
@api.depends('account_ids')
|
||||
def _compute_analytic_account_count(self):
|
||||
for plan in self:
|
||||
plan.account_count = len(plan.account_ids)
|
||||
|
||||
@api.depends('account_ids', 'children_ids')
|
||||
def _compute_all_analytic_account_count(self):
|
||||
for plan in self:
|
||||
plan.all_account_count = self.env['account.analytic.account'].search_count([('plan_id', "child_of", plan.id)])
|
||||
|
||||
@api.depends('children_ids')
|
||||
def _compute_children_count(self):
|
||||
for plan in self:
|
||||
plan.children_count = len(plan.children_ids)
|
||||
|
||||
def action_view_analytical_accounts(self):
|
||||
result = {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "account.analytic.account",
|
||||
"domain": [('plan_id', "child_of", self.id)],
|
||||
"context": {'default_plan_id': self.id},
|
||||
"name": _("Analytical Accounts"),
|
||||
'view_mode': 'list,form',
|
||||
}
|
||||
return result
|
||||
|
||||
def action_view_children_plans(self):
|
||||
result = {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "account.analytic.plan",
|
||||
"domain": [('parent_id', '=', self.id)],
|
||||
"context": {'default_parent_id': self.id,
|
||||
'default_color': self.color},
|
||||
"name": _("Analytical Plans"),
|
||||
'view_mode': 'list,form',
|
||||
}
|
||||
return result
|
||||
|
||||
@api.model
|
||||
def get_relevant_plans(self, **kwargs):
|
||||
""" Returns the list of plans that should be available.
|
||||
This list is computed based on the applicabilities of root plans. """
|
||||
company_id = kwargs.get('company_id', self.env.company.id)
|
||||
record_account_ids = kwargs.get('existing_account_ids', [])
|
||||
all_plans = self.search([
|
||||
('account_ids', '!=', False),
|
||||
'|', ('company_id', '=', company_id), ('company_id', '=', False),
|
||||
])
|
||||
root_plans = self.browse({
|
||||
int(plan.parent_path.split('/')[0])
|
||||
for plan in all_plans
|
||||
}).filtered(lambda p: p._get_applicability(**kwargs) != 'unavailable')
|
||||
# If we have accounts that are already selected (before the applicability rules changed or from a model),
|
||||
# we want the plans that were unavailable to be shown in the list (and in optional, because the previous
|
||||
# percentage could be different from 0)
|
||||
forced_plans = self.env['account.analytic.account'].browse(record_account_ids).exists().mapped(
|
||||
'root_plan_id') - root_plans
|
||||
return sorted([
|
||||
{
|
||||
"id": plan.id,
|
||||
"name": plan.name,
|
||||
"color": plan.color,
|
||||
"applicability": plan._get_applicability(**kwargs) if plan in root_plans else 'optional',
|
||||
"all_account_count": plan.all_account_count
|
||||
}
|
||||
for plan in root_plans + forced_plans
|
||||
], key=lambda d: (d['applicability'], d['id']))
|
||||
|
||||
def _get_applicability(self, **kwargs):
|
||||
""" Returns the applicability of the best applicability line or the default applicability """
|
||||
self.ensure_one()
|
||||
if 'applicability' in kwargs:
|
||||
# For models for example, we want all plans to be visible, so we force the applicability
|
||||
return kwargs['applicability']
|
||||
else:
|
||||
score = 0
|
||||
applicability = self.default_applicability
|
||||
for applicability_rule in self.applicability_ids:
|
||||
score_rule = applicability_rule._get_score(**kwargs)
|
||||
if score_rule > score:
|
||||
applicability = applicability_rule.applicability
|
||||
score = score_rule
|
||||
return applicability
|
||||
|
||||
def _get_default(self):
|
||||
plan = self.env['account.analytic.plan'].sudo().search(
|
||||
['|', ('company_id', '=', False), ('company_id', '=', self.env.company.id)],
|
||||
limit=1)
|
||||
if plan:
|
||||
return plan
|
||||
else:
|
||||
return self.env['account.analytic.plan'].create({
|
||||
'name': 'Default',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
|
||||
class AccountAnalyticApplicability(models.Model):
|
||||
_name = 'account.analytic.applicability'
|
||||
_description = "Analytic Plan's Applicabilities"
|
||||
|
||||
analytic_plan_id = fields.Many2one('account.analytic.plan')
|
||||
business_domain = fields.Selection(
|
||||
selection=[
|
||||
('general', 'Miscellaneous'),
|
||||
],
|
||||
required=True,
|
||||
string='Domain',
|
||||
)
|
||||
applicability = fields.Selection([
|
||||
('optional', 'Optional'),
|
||||
('mandatory', 'Mandatory'),
|
||||
('unavailable', 'Unavailable'),
|
||||
],
|
||||
required=True,
|
||||
string="Applicability",
|
||||
)
|
||||
|
||||
def _get_score(self, **kwargs):
|
||||
""" Gives the score of an applicability with the parameters of kwargs """
|
||||
self.ensure_one()
|
||||
if not kwargs.get('business_domain'):
|
||||
return 0
|
||||
else:
|
||||
return 1 if kwargs.get('business_domain') == self.business_domain else -1
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
group_analytic_accounting = fields.Boolean(string='Analytic Accounting', implied_group='analytic.group_analytic_accounting')
|
||||
Loading…
Add table
Add a link
Reference in a new issue