Car tyres
-
+
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index c554cd6902a..96eaf69da4c 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -13,7 +13,7 @@ from odoo.tools.misc import clean_context, format_date
class HrExpense(models.Model):
_name = "hr.expense"
- _inherit = ['mail.thread', 'mail.activity.mixin']
+ _inherit = ['mail.thread', 'mail.activity.mixin', 'analytic.mixin']
_description = "Expense"
_order = "date desc, id desc"
_check_company_auto = True
@@ -78,8 +78,6 @@ class HrExpense(models.Model):
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=False, store=True, states={'reported': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]}, compute='_compute_currency_id', default=lambda self: self.env.company.currency_id)
currency_rate = fields.Float(compute='_compute_currency_rate')
- analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', check_company=True)
- analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
account_id = fields.Many2one('account.account', compute='_compute_from_product_id_company_id', store=True, readonly=False, precompute=True, string='Account',
domain="[('account_type', 'not in', ('asset_receivable','liability_payable','asset_cash','liability_credit_card')), ('company_id', '=', company_id)]", help="An expense account is expected")
description = fields.Text('Internal Notes', readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]})
@@ -308,17 +306,17 @@ class HrExpense(models.Model):
exp.duplicate_expense_ids = [(6, 0, ids)]
expenses = expenses - exp
- # sgv todo replace with depends?
- @api.onchange('product_id', 'date', 'account_id')
- def _onchange_product_id_date_account_id(self):
- rec = self.env['account.analytic.default'].sudo().account_get(
- product_id=self.product_id.id,
- account_id=self.account_id.id,
- company_id=self.company_id.id,
- date=self.date
- )
- self.analytic_account_id = self.analytic_account_id or rec.analytic_id.id
- self.analytic_tag_ids = self.analytic_tag_ids or rec.analytic_tag_ids.ids
+ @api.depends('product_id', 'account_id')
+ def _compute_analytic_distribution_stored_char(self):
+ for expense in self:
+ distribution = self.env['account.analytic.distribution.model']._get_distributionjson({
+ 'product_id': expense.product_id.id,
+ 'product_categ_id': expense.product_id.categ_id.id,
+ 'account_prefix': expense.account_id.code,
+ 'company_id': expense.company_id.id,
+ })
+ expense.analytic_distribution_stored_char = distribution or expense.analytic_distribution_stored_char
+ expense._compute_analytic_distribution()
@api.constrains('payment_mode')
def _check_payment_mode(self):
@@ -385,7 +383,7 @@ class HrExpense(models.Model):
raise UserError(_('You cannot delete a posted or approved expense.'))
def write(self, vals):
- if 'tax_ids' in vals or 'analytic_account_id' in vals or 'account_id' in vals:
+ if 'tax_ids' in vals or 'analytic_distribution' in vals or 'account_id' in vals:
if any(not expense.is_editable for expense in self):
raise UserError(_('You are not authorized to edit this expense report.'))
if 'reference' in vals:
@@ -537,7 +535,7 @@ Or send your receipts at
-
+
+
+ :
+
+
diff --git a/addons/hr_expense/tests/common.py b/addons/hr_expense/tests/common.py
index 9c6437b2a7e..e88b0434e01 100644
--- a/addons/hr_expense/tests/common.py
+++ b/addons/hr_expense/tests/common.py
@@ -43,11 +43,14 @@ class TestExpenseCommon(AccountTestInvoicingCommon):
cls.env.user.groups_id |= group_expense_manager
# Create analytic account
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
cls.analytic_account_1 = cls.env['account.analytic.account'].create({
'name': 'analytic_account_1',
+ 'plan_id': cls.analytic_plan.id,
})
cls.analytic_account_2 = cls.env['account.analytic.account'].create({
'name': 'analytic_account_2',
+ 'plan_id': cls.analytic_plan.id,
})
# Ensure products can be expensed.
diff --git a/addons/hr_expense/tests/test_expenses.py b/addons/hr_expense/tests/test_expenses.py
index 03fcc094335..6c3cdf213ba 100644
--- a/addons/hr_expense/tests/test_expenses.py
+++ b/addons/hr_expense/tests/test_expenses.py
@@ -128,7 +128,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': self.product_a.id,
'unit_amount': 1000.0,
'tax_ids': [(6, 0, self.company_data['default_tax_purchase'].ids)],
- 'analytic_account_id': self.analytic_account_1.id,
+ 'analytic_distribution': {self.analytic_account_1.id: 100},
'employee_id': self.expense_employee.id,
}),
(0, 0, {
@@ -138,7 +138,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': self.product_b.id,
'unit_amount': 1500.0,
'tax_ids': [(6, 0, self.company_data['default_tax_purchase'].ids)],
- 'analytic_account_id': self.analytic_account_2.id,
+ 'analytic_distribution': {self.analytic_account_2.id: 100},
'currency_id': self.currency_data['currency'].id,
'employee_id': self.expense_employee.id,
}),
@@ -162,7 +162,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': False,
'currency_id': self.company_data['currency'].id,
'tax_line_id': False,
- 'analytic_account_id': False,
+ 'analytic_distribution': False,
},
# Receivable line (foreign currency):
{
@@ -173,7 +173,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': False,
'currency_id': self.currency_data['currency'].id,
'tax_line_id': False,
- 'analytic_account_id': False,
+ 'analytic_distribution': False,
},
# Tax line (foreign currency):
{
@@ -184,7 +184,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': False,
'currency_id': self.currency_data['currency'].id,
'tax_line_id': self.company_data['default_tax_purchase'].id,
- 'analytic_account_id': False,
+ 'analytic_distribution': False,
},
# Tax line (company currency):
{
@@ -195,7 +195,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': False,
'currency_id': self.company_data['currency'].id,
'tax_line_id': self.company_data['default_tax_purchase'].id,
- 'analytic_account_id': False,
+ 'analytic_distribution': False,
},
# Product line (foreign currency):
{
@@ -206,7 +206,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': self.product_b.id,
'currency_id': self.currency_data['currency'].id,
'tax_line_id': False,
- 'analytic_account_id': self.analytic_account_2.id,
+ 'analytic_distribution': {self.analytic_account_2.id: 100},
},
# Product line (company currency):
{
@@ -217,7 +217,7 @@ class TestExpenses(TestExpenseCommon):
'product_id': self.product_a.id,
'currency_id': self.company_data['currency'].id,
'tax_line_id': False,
- 'analytic_account_id': self.analytic_account_1.id,
+ 'analytic_distribution': {self.analytic_account_1.id: 100},
},
])
@@ -258,7 +258,7 @@ class TestExpenses(TestExpenseCommon):
'unit_amount': 700.00,
'tax_ids': [(6, 0, tax.ids)],
'sheet_id': expense.id,
- 'analytic_account_id': self.analytic_account_1.id,
+ 'analytic_distribution': {self.analytic_account_1.id: 100},
'currency_id': self.currency_data['currency'].id,
})
@@ -273,7 +273,6 @@ class TestExpenses(TestExpenseCommon):
# Create Expense Entries
expense.action_sheet_move_create()
self.assertEqual(expense.state, 'post', 'Expense is not in Waiting Payment state')
-
# Should get this result [(0.0, 350.0, -700.0), (318.18, 0.0, 636.36), (31.82, 0.0, 63.64)]
analytic_line = expense.account_move_id.line_ids.analytic_line_ids
self.assertEqual(len(analytic_line), 1)
@@ -327,9 +326,8 @@ class TestExpenses(TestExpenseCommon):
'unit_amount': 350.00,
'tax_ids': [(6, 0, [self.tax_purchase_a.id])],
'sheet_id': expense.id,
- 'analytic_account_id': self.analytic_account_1.id,
+ 'analytic_distribution': {self.analytic_account_1.id: 100},
})
- expense_line._onchange_product_id_date_account_id()
expense.action_submit_sheet()
expense.approve_expense_sheets()
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index edaca813b1c..5904a7c2cc0 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -56,8 +56,10 @@
-
-
+
+
@@ -185,8 +187,13 @@
-
-
+
+
@@ -330,7 +337,6 @@
-
@@ -351,7 +357,6 @@
-
@@ -743,8 +748,12 @@
-
-
+
+
diff --git a/addons/hr_timesheet/data/hr_timesheet_demo.xml b/addons/hr_timesheet/data/hr_timesheet_demo.xml
index 021f1e170a8..274f5b27290 100644
--- a/addons/hr_timesheet/data/hr_timesheet_demo.xml
+++ b/addons/hr_timesheet/data/hr_timesheet_demo.xml
@@ -22,7 +22,6 @@
2.00
-
-60.00
@@ -50,7 +49,6 @@
1.00
-
-30.00
@@ -70,7 +68,6 @@
1
-
-30.0
@@ -91,7 +88,6 @@
3
-
-90.0
@@ -122,7 +118,6 @@
2
-
-60.0
@@ -183,7 +178,6 @@
1
-
-30.0
@@ -244,7 +238,6 @@
2
-
-60.0
@@ -305,7 +298,6 @@
1
-
-30.0
@@ -396,7 +388,6 @@
1
-
-30.0
@@ -417,7 +408,6 @@
1
-
-30.0
@@ -448,7 +438,6 @@
3
-
-90.0
@@ -479,7 +468,6 @@
3
-
-90.0
@@ -500,7 +488,6 @@
1
-
-30.0
@@ -511,7 +498,6 @@
1
-
-30.0
@@ -552,7 +538,6 @@
2
-
-60.0
@@ -623,7 +608,6 @@
3
-
-90.0
@@ -644,7 +628,6 @@
1
-
-30.0
@@ -655,7 +638,6 @@
1
-
-30.0
@@ -686,7 +668,6 @@
1
-
-30.0
@@ -697,7 +678,6 @@
1
-
-30.0
@@ -708,7 +688,6 @@
2
-
-60.0
@@ -729,7 +708,6 @@
1
-
-30.0
@@ -750,7 +728,6 @@
1
-
-30.0
@@ -771,7 +748,6 @@
1
-
-30.0
@@ -892,7 +868,6 @@
1
-
-30.0
@@ -903,7 +878,6 @@
1
-
-30.0
@@ -954,7 +928,6 @@
1
-
-30.0
@@ -995,7 +968,6 @@
2
-
-60.0
@@ -1026,7 +998,6 @@
1
-
-30.0
@@ -1037,7 +1008,6 @@
1
-
-30.0
@@ -1058,7 +1028,6 @@
1
-
-30.0
@@ -1090,7 +1059,6 @@
-60.0
-
@@ -1170,7 +1138,6 @@
3
-
-90.0
@@ -1191,7 +1158,6 @@
2
-
-60.0
@@ -1202,7 +1168,6 @@
3
-
-90.0
@@ -1213,7 +1178,6 @@
1
-
-30.0
@@ -1264,7 +1228,6 @@
2
-
-60.0
@@ -1435,7 +1398,6 @@
1
-
-30.0
@@ -1566,7 +1528,6 @@
1
-
-30.0
@@ -1647,7 +1608,6 @@
1
-
-30.0
@@ -1688,7 +1648,6 @@
2
-
-60.0
diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py
index f174af5a5ea..7b7051a9abb 100644
--- a/addons/hr_timesheet/models/hr_timesheet.py
+++ b/addons/hr_timesheet/models/hr_timesheet.py
@@ -115,7 +115,7 @@ class AccountAnalyticLine(models.Model):
@api.depends('employee_id')
def _compute_user_id(self):
for line in self:
- line.user_id = line.employee_id.user_id if line.employee_id else line._default_user()
+ line.user_id = line.employee_id.user_id if line.employee_id else self._default_user()
@api.depends('employee_id')
def _compute_department_id(self):
@@ -272,20 +272,12 @@ class AccountAnalyticLine(models.Model):
task_analytic_account_id = task._get_task_analytic_account_id()
vals['account_id'] = task_analytic_account_id.id
vals['company_id'] = task_analytic_account_id.company_id.id or task.company_id.id
- if vals.get('tag_ids'):
- vals['tag_ids'] += [Command.link(tag_id.id) for tag_id in task.analytic_tag_ids]
- else:
- vals['tag_ids'] = [Command.set(task.analytic_tag_ids.ids)]
if not task_analytic_account_id.active:
raise UserError(_('You cannot add timesheets to a project or a task linked to an inactive analytic account.'))
# project implies analytic account
elif project and not vals.get('account_id'):
vals['account_id'] = project.analytic_account_id.id
vals['company_id'] = project.analytic_account_id.company_id.id or project.company_id.id
- if vals.get('tag_ids'):
- vals['tag_ids'] += [Command.link(tag_id.id) for tag_id in project.analytic_tag_ids]
- else:
- vals['tag_ids'] = [Command.set(project.analytic_tag_ids.ids)]
if not project.analytic_account_id.active:
raise UserError(_('You cannot add timesheets to a project linked to an inactive analytic account.'))
# force customer partner, from the task or the project
@@ -351,3 +343,6 @@ class AccountAnalyticLine(models.Model):
if len(task_ids) == 1:
return _('Timesheets - %s', task_ids.name)
return _('Timesheets')
+
+ def _default_user(self):
+ return self.env.context.get('user_id', self.env.user.id)
diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py
index 2334793f061..acf756a49bb 100644
--- a/addons/hr_timesheet/tests/test_timesheet.py
+++ b/addons/hr_timesheet/tests/test_timesheet.py
@@ -26,9 +26,14 @@ class TestCommonTimesheet(TransactionCase):
'phone': '42',
})
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan Test',
+ 'company_id': False,
+ })
cls.analytic_account = cls.env['account.analytic.account'].create({
'name': 'Analytic Account for Test Customer',
'partner_id': cls.partner.id,
+ 'plan_id': cls.analytic_plan.id,
'code': 'TEST'
})
@@ -359,8 +364,13 @@ class TestTimesheet(TestCommonTimesheet):
company_2 = self.env['res.company'].create({'name': 'Company 2'})
company_3 = self.env['res.company'].create({'name': 'Company 3'})
+ analytic_plan = self.env['account.analytic.plan'].create({
+ 'name': 'Plan Test',
+ 'company_id': company_3.id
+ })
analytic_account = self.env['account.analytic.account'].create({
'name': 'Aa Aa',
+ 'plan_id': analytic_plan.id,
'company_id': company_3.id,
})
project = self.env['project.project'].create({
@@ -438,44 +448,6 @@ class TestTimesheet(TestCommonTimesheet):
self.assertEqual(self.task1.subtask_effective_hours, 8, 'Hours Spent on Sub-tasks should be 8 hours in Parent Task')
self.task1.child_ids = [Command.clear()]
- def test_log_timesheet_with_analytic_tags(self):
- """ Test whether the analytic tag of the project or task is set on the timesheet.
-
- Test Case:
- ----------
- 1) Create analytic tags
- 2) Add analytic tag in project
- 3) Create timesheet
- 4) Check the default analytic tag of the project and timesheet
- 5) Add analytic tag in task
- 6) Check the analytic tag of the timesheet and task
- """
- Timesheet = self.env['account.analytic.line'].with_user(self.user_employee)
-
- share_capital_tag, office_furn_tag = self.env['account.analytic.tag'].create([
- {'name': 'Share capital'},
- {'name': 'Office Furniture'},
- ])
-
- self.project_customer.analytic_tag_ids = [Command.set((share_capital_tag + office_furn_tag).ids)]
-
- timesheet = Timesheet.create({
- 'project_id': self.project_customer.id,
- 'name': 'my first timesheet',
- 'unit_amount': 4,
- })
- self.assertEqual(timesheet.tag_ids, self.project_customer.analytic_tag_ids)
-
- self.task2.analytic_tag_ids = [Command.set((share_capital_tag + office_furn_tag).ids)]
-
- timesheet1 = Timesheet.create({
- 'project_id': self.project_customer.id,
- 'task_id': self.task2.id,
- 'name': 'my first timesheet',
- 'unit_amount': 4,
- })
- self.assertEqual(timesheet1.tag_ids, self.task2.analytic_tag_ids)
-
def test_ensure_product_uom_set_in_timesheet(self):
self.assertFalse(self.project_customer.timesheet_ids, 'No timesheet should be recorded in this project')
self.assertFalse(self.project_customer.total_timesheet_time, 'The total time recorded should be equal to 0 since no timesheet is recorded.')
diff --git a/addons/hr_timesheet/views/hr_timesheet_views.xml b/addons/hr_timesheet/views/hr_timesheet_views.xml
index 1b52e0cc96c..de3f76ecb57 100644
--- a/addons/hr_timesheet/views/hr_timesheet_views.xml
+++ b/addons/hr_timesheet/views/hr_timesheet_views.xml
@@ -28,7 +28,6 @@
-
@@ -140,7 +139,6 @@
-
@@ -181,7 +179,6 @@
-
diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml
index 6484315235d..0424b2ccfc2 100644
--- a/addons/hr_timesheet/views/project_views.xml
+++ b/addons/hr_timesheet/views/project_views.xml
@@ -152,7 +152,6 @@
-
diff --git a/addons/mrp_account/tests/test_analytic_account.py b/addons/mrp_account/tests/test_analytic_account.py
index becba02cb9d..598c0513bcc 100644
--- a/addons/mrp_account/tests/test_analytic_account.py
+++ b/addons/mrp_account/tests/test_analytic_account.py
@@ -12,7 +12,14 @@ class TestAnalyticAccount(TransactionCase):
super().setUpClass()
cls.env.user.write({'groups_id': [(4, cls.env.ref('analytic.group_analytic_accounting').id),]})
- cls.analytic_account = cls.env['account.analytic.account'].create({'name': 'test_analytic_account'})
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan',
+ 'company_id': False,
+ })
+ cls.analytic_account = cls.env['account.analytic.account'].create({
+ 'name': 'test_analytic_account',
+ 'plan_id': cls.analytic_plan.id,
+ })
cls.workcenter = cls.env['mrp.workcenter'].create({
'name': 'Workcenter',
'default_capacity': 1,
@@ -152,7 +159,8 @@ class TestAnalyticAccount(TransactionCase):
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
# set wc analytic account to be different from the one on the bom
- wc_analytic_account = self.env['account.analytic.account'].create({'name': 'wc_analytic_account'})
+ analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
+ wc_analytic_account = self.env['account.analytic.account'].create({'name': 'wc_analytic_account', 'plan_id': analytic_plan.id})
self.workcenter.costs_hour_account_id = wc_analytic_account
# create a mo
@@ -216,7 +224,8 @@ class TestAnalyticAccount(TransactionCase):
self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 1)
# Create a new analytic account
- new_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account_2'})
+ analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
+ new_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account_2', 'plan_id': analytic_plan.id})
# Change the MO analytic account
mo.analytic_account_id = new_analytic_account
self.assertEqual(mo.move_raw_ids.analytic_account_line_id.account_id.id, new_analytic_account.id)
diff --git a/addons/project/data/project_demo.xml b/addons/project/data/project_demo.xml
index dbaf4e51e9a..74e930c4d79 100644
--- a/addons/project/data/project_demo.xml
+++ b/addons/project/data/project_demo.xml
@@ -40,50 +40,17 @@
Office Design
+
Research & Development
+
Renovations
-
-
-
-
- Analysis
- 1
-
-
-
- Design
- 2
-
-
-
- Training
- 3
-
-
-
- Research
- 4
-
-
-
- Development
- 5
-
-
-
- Modification
- 6
-
-
-
- Decoration
- 7
+
@@ -129,7 +96,6 @@
-
project.project
@@ -146,7 +112,6 @@
-
@@ -394,7 +359,6 @@
-
project.task
@@ -488,7 +452,6 @@
11
-
project.task
@@ -529,7 +492,6 @@
9
-
project.task
@@ -701,7 +663,6 @@
Preparation
-
30
@@ -725,7 +686,6 @@
Customer analysis + Architecture
7
-
project.task
diff --git a/addons/project/models/__init__.py b/addons/project/models/__init__.py
index 4bf11751b09..3f3151245a2 100644
--- a/addons/project/models/__init__.py
+++ b/addons/project/models/__init__.py
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import analytic_account
-from . import analytic_account_tag
from . import project_milestone
from . import project_project_stage
from . import project_task_recurrence
@@ -11,6 +10,7 @@ from . import project_task_stage_personal
from . import project
from . import project_collaborator
from . import project_update
+from . import company
from . import res_config_settings
from . import res_partner
from . import digest
diff --git a/addons/project/models/analytic_account_tag.py b/addons/project/models/analytic_account_tag.py
deleted file mode 100644
index f523a14abbb..00000000000
--- a/addons/project/models/analytic_account_tag.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-class AccountAnalyticTag(models.Model):
- _inherit = 'account.analytic.tag'
-
- task_ids = fields.Many2many('project.task', string='Tasks')
- project_ids = fields.Many2many('project.project', string='Projects')
diff --git a/addons/project/models/company.py b/addons/project/models/company.py
new file mode 100644
index 00000000000..079e10cbb31
--- /dev/null
+++ b/addons/project/models/company.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from odoo import fields, models
+
+
+class ResCompany(models.Model):
+ _name = "res.company"
+ _inherit = "res.company"
+
+ analytic_plan_id = fields.Many2one(
+ 'account.analytic.plan',
+ string="Default Plan",
+ check_company=True,
+ readonly=False,
+ compute="_compute_analytic_plan_id",
+ help="Default Plan for a new analytic account for projects")
+
+ def _compute_analytic_plan_id(self):
+ for company in self:
+ company.analytic_plan_id = self.env['account.analytic.plan'].with_company(company)._get_default()
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index ab6204a2f21..f78f09e0672 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -303,7 +303,6 @@ class Project(models.Model):
help="Analytic account to which this project is linked for financial management. "
"Use an analytic account to record cost and revenue on your project.")
analytic_account_balance = fields.Monetary(related="analytic_account_id.balance")
- analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
favorite_user_ids = fields.Many2many(
'res.users', 'project_favorite_user_rel', 'project_id', 'user_id',
@@ -945,11 +944,12 @@ class Project(models.Model):
@api.model
def _create_analytic_account_from_values(self, values):
+ company = self.env['res.company'].browse(values.get('company_id')) if values.get('company_id') else self.env.company
analytic_account = self.env['account.analytic.account'].create({
'name': values.get('name', _('Unknown Analytic Account')),
- 'company_id': values.get('company_id') or self.env.company.id,
+ 'company_id': company.id,
'partner_id': values.get('partner_id'),
- 'active': True,
+ 'plan_id': company.analytic_plan_id.id,
})
return analytic_account
@@ -959,6 +959,7 @@ class Project(models.Model):
'name': project.name,
'company_id': project.company_id.id,
'partner_id': project.partner_id.id,
+ 'plan_id': project.company_id.analytic_plan_id.id,
'active': True,
})
project.write({'analytic_account_id': analytic_account.id})
@@ -1307,8 +1308,6 @@ class Task(models.Model):
"If empty, the analytic account of the project will be used.")
is_analytic_account_id_changed = fields.Boolean('Is Analytic Account Manually Changed', compute='_compute_is_analytic_account_id_changed', store=True)
project_analytic_account_id = fields.Many2one('account.analytic.account', string='Project Analytic Account', related='project_id.analytic_account_id')
- analytic_tag_ids = fields.Many2many('account.analytic.tag', string="Analytic Tags",
- domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", check_company=True)
@property
def SELF_READABLE_FIELDS(self):
@@ -1835,8 +1834,6 @@ class Task(models.Model):
project = self.env['project.project'].browse(project_id)
if project.analytic_account_id:
vals['analytic_account_id'] = project.analytic_account_id.id
- if project.analytic_tag_ids:
- vals['analytic_tag_ids'] = [Command.set(project.analytic_tag_ids.ids)]
else:
vals['user_ids'] = [Command.link(self.env.user.id)]
diff --git a/addons/project/models/res_config_settings.py b/addons/project/models/res_config_settings.py
index ff196aa361f..2552d09c985 100644
--- a/addons/project/models/res_config_settings.py
+++ b/addons/project/models/res_config_settings.py
@@ -16,6 +16,14 @@ class ResConfigSettings(models.TransientModel):
group_project_task_dependencies = fields.Boolean("Task Dependencies", implied_group="project.group_project_task_dependencies")
group_project_milestone = fields.Boolean('Milestones', implied_group='project.group_project_milestone', group='base.group_portal,base.group_user')
+ # Analytic Accounting
+ analytic_plan_id = fields.Many2one(
+ comodel_name='account.analytic.plan',
+ string="Default Plan",
+ readonly=False,
+ related='company_id.analytic_plan_id',
+ )
+
@api.model
def _get_basic_project_domain(self):
return []
diff --git a/addons/project/tests/test_project_profitability.py b/addons/project/tests/test_project_profitability.py
index a42ef01a2e4..a983118c701 100644
--- a/addons/project/tests/test_project_profitability.py
+++ b/addons/project/tests/test_project_profitability.py
@@ -14,9 +14,14 @@ class TestProjectProfitabilityCommon(TransactionCase):
'name': 'Georges',
'email': 'georges@project-profitability.com'})
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan A',
+ 'company_id': False,
+ })
cls.analytic_account = cls.env['account.analytic.account'].create({
'name': 'Project - AA',
'code': 'AA-1234',
+ 'plan_id': cls.analytic_plan.id,
})
cls.project = cls.env['project.project'].with_context({'mail_create_nolog': True}).create({
'name': 'Project',
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 9beef92d51c..14b2406a8fe 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -519,7 +519,6 @@
-
@@ -1319,7 +1318,6 @@
-
diff --git a/addons/project/views/res_config_settings_views.xml b/addons/project/views/res_config_settings_views.xml
index 349c9f222df..081df272714 100644
--- a/addons/project/views/res_config_settings_views.xml
+++ b/addons/project/views/res_config_settings_views.xml
@@ -118,6 +118,10 @@
+
+
+
+
diff --git a/addons/project_hr_expense/data/project_hr_expense_demo.xml b/addons/project_hr_expense/data/project_hr_expense_demo.xml
index 31038365c74..d75d58d7dc1 100644
--- a/addons/project_hr_expense/data/project_hr_expense_demo.xml
+++ b/addons/project_hr_expense/data/project_hr_expense_demo.xml
@@ -2,10 +2,10 @@
-
+
-
+
diff --git a/addons/project_hr_expense/models/project.py b/addons/project_hr_expense/models/project.py
index d4688d14176..4c4acbbdc80 100644
--- a/addons/project_hr_expense/models/project.py
+++ b/addons/project_hr_expense/models/project.py
@@ -16,14 +16,11 @@ class Project(models.Model):
if not self.analytic_account_id:
self.expenses_count = 0
return
- expenses_data = self.env['hr.expense']._read_group([
- ('analytic_account_id', '!=', False),
- ('analytic_account_id', 'in', self.analytic_account_id.ids)
- ],
- ['analytic_account_id'], ['analytic_account_id'])
- mapped_data = {data['analytic_account_id'][0]: data['analytic_account_id_count'] for data in expenses_data}
for project in self:
- project.expenses_count = mapped_data.get(project.analytic_account_id.id, 0)
+ expenses = self.env['hr.expense'].search([
+ ('analytic_distribution_stored_char', '=ilike', f'%"{project.analytic_account_id.id}":%')
+ ])
+ project.expenses_count = len(expenses)
# ----------------------------
# Actions
@@ -36,7 +33,7 @@ class Project(models.Model):
action.update({
'display_name': _('Expenses'),
'views': [[False, 'tree'], [False, 'form'], [False, 'kanban'], [False, 'graph'], [False, 'pivot']],
- 'context': {'default_analytic_account_id': self.analytic_account_id.id},
+ 'context': {'default_analytic_distribution': {self.analytic_account_id.id: 100}},
'domain': domain or [('id', 'in', expense_ids)],
})
if len(expense_ids) == 1:
@@ -68,7 +65,7 @@ class Project(models.Model):
return {}
can_see_expense = with_action and self.user_has_groups('hr_expense.group_hr_expense_team_approver')
expenses_read_group = self.env['hr.expense'].sudo()._read_group(
- [('analytic_account_id', 'in', self.analytic_account_id.ids),
+ [('analytic_distribution_stored_char', '=ilike', f'%"{self.analytic_account_id.id}":%'),
('is_refused', '=', False),
('state', 'in', ['approved', 'done'])],
['untaxed_amount', 'ids:array_agg(id)'],
@@ -92,7 +89,7 @@ class Project(models.Model):
def _get_profitability_aal_domain(self):
return expression.AND([
super()._get_profitability_aal_domain(),
- ['|', ('move_id', '=', False), ('move_id.expense_id', '=', False)],
+ ['|', ('move_line_id', '=', False), ('move_line_id.expense_id', '=', False)],
])
def _get_profitability_items(self, with_action=True):
diff --git a/addons/project_hr_expense/tests/test_project_profitability.py b/addons/project_hr_expense/tests/test_project_profitability.py
index b90cee37b38..4338bfd12a8 100644
--- a/addons/project_hr_expense/tests/test_project_profitability.py
+++ b/addons/project_hr_expense/tests/test_project_profitability.py
@@ -42,7 +42,7 @@ class TestProjectHrExpenseProfitability(TestProjectProfitabilityCommon, TestProj
'product_id': self.product_a.id,
'unit_amount': 350.00,
'company_id': self.project.company_id.id,
- 'analytic_account_id': self.project.analytic_account_id.id,
+ 'analytic_distribution': {self.project.analytic_account_id.id: 100},
})
expense_sheet = self.check_project_profitability_before_creating_and_approving_expense_sheet(
diff --git a/addons/project_purchase/data/project_purchase_demo.xml b/addons/project_purchase/data/project_purchase_demo.xml
index bfae67acce3..b40f186a59d 100644
--- a/addons/project_purchase/data/project_purchase_demo.xml
+++ b/addons/project_purchase/data/project_purchase_demo.xml
@@ -3,7 +3,7 @@
-
+
-
+
\ No newline at end of file
diff --git a/addons/project_purchase/models/project.py b/addons/project_purchase/models/project.py
index 4097301a1f8..1bd1c46ce6b 100644
--- a/addons/project_purchase/models/project.py
+++ b/addons/project_purchase/models/project.py
@@ -17,12 +17,11 @@ class Project(models.Model):
if not self.analytic_account_id:
self.purchase_orders_count = 0
return
- purchase_orders_data = self.env['purchase.order.line']._read_group([
- ('account_analytic_id', 'in', self.analytic_account_id.ids)
- ], ['account_analytic_id', 'order_id:count_distinct'], ['account_analytic_id'])
- mapped_data = dict([(data['account_analytic_id'][0], data['order_id']) for data in purchase_orders_data])
for project in self:
- project.purchase_orders_count = mapped_data.get(project.analytic_account_id.id, 0)
+ purchase_orders = self.env['purchase.order'].search([
+ ('order_line.analytic_distribution_stored_char', '=ilike', f'%"{project.analytic_account_id.id}":%')
+ ])
+ project.purchase_orders_count = len(purchase_orders)
# ----------------------------
# Actions
@@ -30,8 +29,7 @@ class Project(models.Model):
def action_open_project_purchase_orders(self):
purchase_orders = self.env['purchase.order'].search([
- ('order_line.account_analytic_id', '!=', False),
- ('order_line.account_analytic_id', 'in', self.analytic_account_id.ids)
+ ('order_line.analytic_distribution_stored_char', '=ilike', f'%"{self.analytic_account_id.id}":%')
])
action_window = {
'name': _('Purchase Orders'),
@@ -94,7 +92,7 @@ class Project(models.Model):
def _get_profitability_aal_domain(self):
return expression.AND([
super()._get_profitability_aal_domain(),
- ['|', ('move_id', '=', False), ('move_id.purchase_line_id', '=', False)],
+ ['|', ('move_line_id', '=', False), ('move_line_id.purchase_line_id', '=', False)],
])
def _get_profitability_labels(self):
@@ -111,7 +109,7 @@ class Project(models.Model):
profitability_items = super()._get_profitability_items(with_action)
if self.analytic_account_id:
purchase_order_line_read = self.env['purchase.order.line'].sudo().search_read([
- ('account_analytic_id', 'in', self.analytic_account_id.ids),
+ ('analytic_distribution_stored_char', '=ilike', f'%"{self.analytic_account_id.id}":%'),
('state', 'in', ['purchase', 'done']),
'|',
('qty_invoiced', '>', 0),
diff --git a/addons/project_purchase/models/purchase_order_line.py b/addons/project_purchase/models/purchase_order_line.py
index 7c9f406f1a1..85efce71aa2 100644
--- a/addons/project_purchase/models/purchase_order_line.py
+++ b/addons/project_purchase/models/purchase_order_line.py
@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+from odoo import models
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'
- def _default_account_analytic_id(self):
+ def _compute_analytic_distribution(self):
+ super()._compute_analytic_distribution()
if self._context.get('project_id'):
- return self.env['project.project'].browse(self._context['project_id']).analytic_account_id
- return False
-
- account_analytic_id = fields.Many2one(default=_default_account_analytic_id)
+ self.analytic_distribution = {self.env['project.project'].browse(self._context['project_id']).analytic_account_id: 100}
diff --git a/addons/project_sale_expense/models/project_project.py b/addons/project_sale_expense/models/project_project.py
index fc1493e749c..bab5fa56d9a 100644
--- a/addons/project_sale_expense/models/project_project.py
+++ b/addons/project_sale_expense/models/project_project.py
@@ -15,7 +15,7 @@ class Project(models.Model):
can_see_expense = with_action and self.user_has_groups('hr_expense.group_hr_expense_team_approver')
expenses_read_group = self.env['hr.expense'].sudo()._read_group(
[
- ('analytic_account_id', 'in', self.analytic_account_id.ids),
+ ('analytic_distribution_stored_char', '=ilike', f'%"{self.analytic_account_id.id}":%'),
('is_refused', '=', False),
('state', 'in', ['approved', 'done']),
],
diff --git a/addons/project_sale_expense/tests/test_project_profitability.py b/addons/project_sale_expense/tests/test_project_profitability.py
index 964fa270057..4d6303bdacc 100644
--- a/addons/project_sale_expense/tests/test_project_profitability.py
+++ b/addons/project_sale_expense/tests/test_project_profitability.py
@@ -16,7 +16,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
'product_id': self.company_data['product_order_sales_price'].id,
'unit_amount': self.company_data['product_order_sales_price'].list_price,
'employee_id': self.expense_employee.id,
- 'analytic_account_id': self.project.analytic_account_id.id,
+ 'analytic_distribution': {self.project.analytic_account_id.id: 100},
'sale_order_id': self.sale_order.id,
})
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index a795a85d0d4..2d1346680f2 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
-import json
from markupsafe import escape, Markup
from pytz import timezone, UTC
@@ -909,6 +908,7 @@ class PurchaseOrder(models.Model):
class PurchaseOrderLine(models.Model):
_name = 'purchase.order.line'
+ _inherit = 'analytic.mixin'
_description = 'Purchase Order Line'
_order = 'order_id, sequence, id'
@@ -936,8 +936,7 @@ class PurchaseOrderLine(models.Model):
price_tax = fields.Float(compute='_compute_amount', string='Tax', store=True)
order_id = fields.Many2one('purchase.order', string='Order Reference', index=True, required=True, ondelete='cascade')
- account_analytic_id = fields.Many2one('account.analytic.account', store=True, string='Analytic Account', compute='_compute_account_analytic_id', readonly=False)
- analytic_tag_ids = fields.Many2many('account.analytic.tag', store=True, string='Analytic Tags', compute='_compute_analytic_tag_ids', readonly=False)
+
company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
state = fields.Selection(related='order_id.state', store=True)
@@ -1131,31 +1130,19 @@ class PurchaseOrderLine(models.Model):
else:
return datetime.today() + relativedelta(days=seller.delay if seller else 0)
- @api.depends('product_id', 'date_order')
- def _compute_account_analytic_id(self):
- for rec in self:
- if not rec.display_type:
- default_analytic_account = rec.env['account.analytic.default'].sudo().account_get(
- product_id=rec.product_id.id,
- partner_id=rec.order_id.partner_id.id,
- user_id=rec.env.uid,
- date=rec.date_order,
- company_id=rec.company_id.id,
- )
- rec.account_analytic_id = default_analytic_account.analytic_id
-
- @api.depends('product_id', 'date_order')
- def _compute_analytic_tag_ids(self):
- for rec in self:
- if not rec.display_type:
- default_analytic_account = rec.env['account.analytic.default'].sudo().account_get(
- product_id=rec.product_id.id,
- partner_id=rec.order_id.partner_id.id,
- user_id=rec.env.uid,
- date=rec.date_order,
- company_id=rec.company_id.id,
- )
- rec.analytic_tag_ids = default_analytic_account.analytic_tag_ids
+ @api.depends('product_id', 'order_id.partner_id')
+ def _compute_analytic_distribution_stored_char(self):
+ for line in self:
+ if not line.display_type:
+ distribution = self.env['account.analytic.distribution.model']._get_distributionjson({
+ "product_id": line.product_id.id,
+ "product_categ_id": line.product_id.categ_id.id,
+ "partner_id": line.order_id.partner_id.id,
+ "partner_category_id": line.order_id.partner_id.category_id.ids,
+ "company_id": line.company_id.id,
+ })
+ line.analytic_distribution_stored_char = distribution or line.analytic_distribution_stored_char
+ line._compute_analytic_distribution()
@api.onchange('product_id')
def onchange_product_id(self):
@@ -1352,8 +1339,7 @@ class PurchaseOrderLine(models.Model):
'quantity': self.qty_to_invoice,
'price_unit': self.currency_id._convert(self.price_unit, aml_currency, self.company_id, date, round=False),
'tax_ids': [(6, 0, self.taxes_id.ids)],
- 'analytic_account_id': self.account_analytic_id.id,
- 'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
+ 'analytic_distribution': self.analytic_distribution,
'purchase_line_id': self.id,
}
diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py
index ae8decb2229..3305a43b038 100644
--- a/addons/purchase/report/purchase_report.py
+++ b/addons/purchase/report/purchase_report.py
@@ -48,7 +48,6 @@ class PurchaseReport(models.Model):
product_tmpl_id = fields.Many2one('product.template', 'Product Template', readonly=True)
country_id = fields.Many2one('res.country', 'Partner Country', readonly=True)
fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', readonly=True)
- account_analytic_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True)
commercial_partner_id = fields.Many2one('res.partner', 'Commercial Entity', readonly=True)
weight = fields.Float('Gross Weight', readonly=True)
volume = fields.Float('Volume', readonly=True)
@@ -90,7 +89,6 @@ class PurchaseReport(models.Model):
(sum(l.product_qty * l.price_unit / COALESCE(po.currency_rate, 1.0))/NULLIF(sum(l.product_qty/line_uom.factor*product_uom.factor),0.0))::decimal(16,2) * currency_table.rate as price_average,
partner.country_id as country_id,
partner.commercial_partner_id as commercial_partner_id,
- analytic_account.id as account_analytic_id,
sum(p.weight * l.product_qty/line_uom.factor*product_uom.factor) as weight,
sum(p.volume * l.product_qty/line_uom.factor*product_uom.factor) as volume,
sum(l.price_subtotal / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * currency_table.rate as untaxed_total,
@@ -114,7 +112,6 @@ class PurchaseReport(models.Model):
left join product_template t on (p.product_tmpl_id=t.id)
left join uom_uom line_uom on (line_uom.id=l.product_uom)
left join uom_uom product_uom on (product_uom.id=t.uom_id)
- left join account_analytic_account analytic_account on (l.account_analytic_id = analytic_account.id)
left join currency_rate cr on (cr.currency_id = po.currency_id and
cr.company_id = po.company_id and
cr.date_start <= coalesce(po.date_order, now()) and
@@ -152,7 +149,6 @@ class PurchaseReport(models.Model):
product_uom.factor,
partner.country_id,
partner.commercial_partner_id,
- analytic_account.id,
po.id,
currency_table.rate
"""
diff --git a/addons/purchase/tests/test_purchase_invoice.py b/addons/purchase/tests/test_purchase_invoice.py
index 898a4eaca4f..0541be8a62f 100644
--- a/addons/purchase/tests/test_purchase_invoice.py
+++ b/addons/purchase/tests/test_purchase_invoice.py
@@ -313,19 +313,21 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon):
self.assertEqual(move.amount_total, 0.01)
- def test_vendor_bill_analytic_account_default_change(self):
+ def test_vendor_bill_analytic_account_model_change(self):
""" Tests whether, when an analytic account rule is set, and user changes manually the analytic account on
the po, it is the same that is mentioned in the bill.
"""
# Required for `analytic.group_analytic_accounting` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
- analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'})
- analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual'})
+ analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
+ analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan.id})
+ analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual', 'plan_id': analytic_plan.id})
- self.env['account.analytic.default'].create({
- 'analytic_id': analytic_account_default.id,
+ self.env['account.analytic.distribution.model'].create({
+ 'analytic_distribution': {analytic_account_default.id: 100},
'product_id': self.product_order.id,
})
+ analytic_distribution_manual = {analytic_account_manual.id: 100}
po_form = Form(self.env['purchase.order'].with_context(tracking_disable=True))
po_form.partner_id = self.partner_a
@@ -334,32 +336,32 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon):
po_line_form.product_id = self.product_order
po_line_form.product_qty = 1.0
po_line_form.price_unit = 10
- po_line_form.account_analytic_id = analytic_account_manual
+ po_line_form.analytic_distribution = analytic_distribution_manual
purchase_order = po_form.save()
purchase_order.button_confirm()
purchase_order.action_create_invoice()
aml = self.env['account.move.line'].search([('purchase_line_id', '=', purchase_order.order_line.id)])
- self.assertRecordValues(aml, [{'analytic_account_id': analytic_account_manual.id}])
+ self.assertRecordValues(aml, [{'analytic_distribution': analytic_distribution_manual}])
- def test_vendor_bill_analytic_account_product_change(self):
+ def test_purchase_order_analytic_account_product_change(self):
self.env.user.groups_id += self.env.ref('account.group_account_readonly')
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
- analytic_account_super = self.env['account.analytic.account'].create({'name': 'Super Account'})
- analytic_account_great = self.env['account.analytic.account'].create({'name': 'Great Account'})
+ analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
+ analytic_account_super = self.env['account.analytic.account'].create({'name': 'Super Account', 'plan_id': analytic_plan.id})
+ analytic_account_great = self.env['account.analytic.account'].create({'name': 'Great Account', 'plan_id': analytic_plan.id})
super_product = self.env['product.product'].create({'name': 'Super Product'})
great_product = self.env['product.product'].create({'name': 'Great Product'})
- product_no_account = self.env['product.product'].create({'name': 'Product No Account'})
- self.env['account.analytic.default'].create([
+ self.env['account.analytic.distribution.model'].create([
{
- 'analytic_id': analytic_account_super.id,
+ 'analytic_distribution': {analytic_account_super.id: 100},
'product_id': super_product.id,
},
{
- 'analytic_id': analytic_account_great.id,
+ 'analytic_distribution': {analytic_account_great.id: 100},
'product_id': great_product.id,
},
])
@@ -371,23 +373,21 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon):
purchase_order = po_form.save()
purchase_order_line = purchase_order.order_line
- self.assertEqual(purchase_order_line.account_analytic_id.id, analytic_account_super.id, "The analytic account should be set to 'Super Account'")
+ self.assertEqual(purchase_order_line.analytic_distribution, {analytic_account_super.id: 100}, "The analytic account should be set to 'Super Account'")
purchase_order_line.write({'product_id': great_product.id})
- self.assertEqual(purchase_order_line.account_analytic_id.id, analytic_account_great.id, "The analytic account should be set to 'Great Account'")
- purchase_order_line.write({'product_id': product_no_account.id})
- self.assertFalse(purchase_order_line.account_analytic_id.id, "The analytic account should not be set")
+ self.assertEqual(purchase_order_line.analytic_distribution, {analytic_account_great.id: 100}, "The analytic account should be set to 'Great Account'")
- po_no_analytic_account = self.env['purchase.order'].create({
+ po_no_analytic_distribution = self.env['purchase.order'].create({
'partner_id': self.env.ref('base.res_partner_1').id,
})
- pol_no_analytic_account = self.env['purchase.order.line'].create({
+ pol_no_analytic_distribution = self.env['purchase.order.line'].create({
'name': super_product.name,
'product_id': super_product.id,
- 'order_id': po_no_analytic_account.id,
- 'account_analytic_id': False,
+ 'order_id': po_no_analytic_distribution.id,
+ 'analytic_distribution': False,
})
- po_no_analytic_account.button_confirm()
- self.assertFalse(pol_no_analytic_account.account_analytic_id.id, "The compute should not overwrite what the user has set.")
+ po_no_analytic_distribution.button_confirm()
+ self.assertFalse(pol_no_analytic_distribution.analytic_distribution, "The compute should not overwrite what the user has set.")
def test_sequence_invoice_lines_from_multiple_purchases(self):
"""Test if the invoice lines are sequenced by purchase order when creating an invoice
diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml
index 6b0ac9eb736..7ab3486f655 100644
--- a/addons/purchase/views/purchase_views.xml
+++ b/addons/purchase/views/purchase_views.xml
@@ -237,8 +237,13 @@
force_save="1" domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/>
-
-
+
+
+
@@ -282,8 +287,12 @@
-
-
+
+
+
@@ -733,7 +742,12 @@
domain="[('type_tax_use', '=', 'purchase')]"/>
-
+
+
+
diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py
index a2bdef796e0..1d8b9383648 100644
--- a/addons/purchase_requisition/models/purchase_requisition.py
+++ b/addons/purchase_requisition/models/purchase_requisition.py
@@ -163,6 +163,7 @@ class PurchaseRequisition(models.Model):
class PurchaseRequisitionLine(models.Model):
_name = "purchase.requisition.line"
+ _inherit = 'analytic.mixin'
_description = "Purchase Requisition Line"
_rec_name = 'product_id'
@@ -175,8 +176,6 @@ class PurchaseRequisitionLine(models.Model):
qty_ordered = fields.Float(compute='_compute_ordered_qty', string='Ordered Quantities')
requisition_id = fields.Many2one('purchase.requisition', required=True, string='Purchase Agreement', ondelete='cascade')
company_id = fields.Many2one('res.company', related='requisition_id.company_id', string='Company', store=True, readonly=True)
- account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
- analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
schedule_date = fields.Date(string='Scheduled Date')
supplier_info_ids = fields.One2many('product.supplierinfo', 'purchase_requisition_line_id')
@@ -266,6 +265,5 @@ class PurchaseRequisitionLine(models.Model):
'price_unit': price_unit,
'taxes_id': [(6, 0, taxes_ids)],
'date_planned': date_planned,
- 'account_analytic_id': self.account_analytic_id.id,
- 'analytic_tag_ids': self.analytic_tag_ids.ids,
+ 'analytic_distribution': self.analytic_distribution,
}
diff --git a/addons/purchase_requisition/views/purchase_requisition_views.xml b/addons/purchase_requisition/views/purchase_requisition_views.xml
index 097e747c82e..f4282c88c4a 100644
--- a/addons/purchase_requisition/views/purchase_requisition_views.xml
+++ b/addons/purchase_requisition/views/purchase_requisition_views.xml
@@ -161,8 +161,13 @@
-
-
+
+
+
diff --git a/addons/purchase_stock/views/purchase_views.xml b/addons/purchase_stock/views/purchase_views.xml
index 63573286768..63c8508cfee 100644
--- a/addons/purchase_stock/views/purchase_views.xml
+++ b/addons/purchase_stock/views/purchase_views.xml
@@ -52,7 +52,7 @@
-
+
diff --git a/addons/sale/models/account_move_line.py b/addons/sale/models/account_move_line.py
index 6cb4f6d4e35..5e989dac515 100644
--- a/addons/sale/models/account_move_line.py
+++ b/addons/sale/models/account_move_line.py
@@ -21,26 +21,26 @@ class AccountMoveLine(models.Model):
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
values['sale_line_ids'] = [(6, None, self.sale_line_ids.ids)]
- def _prepare_analytic_line(self):
- """ Note: This method is called only on the move.line that having an analytic account, and
+ def _prepare_analytic_lines(self):
+ """ Note: This method is called only on the move.line that having an analytic distribution, and
so that should create analytic entries.
"""
- values_list = super(AccountMoveLine, self)._prepare_analytic_line()
+ values_list = super(AccountMoveLine, self)._prepare_analytic_lines()
# filter the move lines that can be reinvoiced: a cost (negative amount) analytic line without SO line but with a product can be reinvoiced
move_to_reinvoice = self.env['account.move.line']
- for index, move_line in enumerate(self):
- values = values_list[index]
- if 'so_line' not in values:
- if move_line._sale_can_be_reinvoice():
- move_to_reinvoice |= move_line
+ if len(values_list) > 0:
+ for index, move_line in enumerate(self):
+ values = values_list[index]
+ if 'so_line' not in values:
+ if move_line._sale_can_be_reinvoice():
+ move_to_reinvoice |= move_line
# insert the sale line in the create values of the analytic entries
if move_to_reinvoice:
map_sale_line_per_move = move_to_reinvoice._sale_create_reinvoice_sale_line()
-
for values in values_list:
- sale_line = map_sale_line_per_move.get(values.get('move_id'))
+ sale_line = map_sale_line_per_move.get(values.get('move_line_id'))
if sale_line:
values['so_line'] = sale_line.id
@@ -133,23 +133,20 @@ class AccountMoveLine(models.Model):
""" Get the mapping of move.line with the sale.order record on which its analytic entries should be reinvoiced
:return a dict where key is the move line id, and value is sale.order record (or None).
"""
- analytic_accounts = self.mapped('analytic_account_id')
-
- # link the analytic account with its open SO by creating a map: {AA.id: sale.order}, if we find some analytic accounts
mapping = {}
- if analytic_accounts: # first, search for the open sales order
- sale_orders = self.env['sale.order'].search([('analytic_account_id', 'in', analytic_accounts.ids), ('state', '=', 'sale')], order='create_date DESC')
- for sale_order in sale_orders:
- mapping[sale_order.analytic_account_id.id] = sale_order
-
- analytic_accounts_without_open_order = analytic_accounts.filtered(lambda account: not mapping.get(account.id))
- if analytic_accounts_without_open_order: # then, fill the blank with not open sales orders
- sale_orders = self.env['sale.order'].search([('analytic_account_id', 'in', analytic_accounts_without_open_order.ids)], order='create_date DESC')
- for sale_order in sale_orders:
- mapping[sale_order.analytic_account_id.id] = sale_order
+ for move_line in self:
+ if move_line.analytic_distribution:
+ distribution_json = move_line.analytic_distribution
+ sale_order = self.env['sale.order'].search([('analytic_account_id', 'in', list(int(account_id) for account_id in distribution_json.keys())),
+ ('state', '=', 'sale')], order='create_date ASC', limit=1)
+ if sale_order:
+ mapping[move_line.id] = sale_order
+ else:
+ sale_order = self.env['sale.order'].search([('analytic_account_id', 'in', list(int(account_id) for account_id in distribution_json.keys()))], order='create_date ASC', limit=1)
+ mapping[move_line.id] = sale_order
# map of AAL index with the SO on which it needs to be reinvoiced. Maybe be None if no SO found
- return {move_line.id: mapping.get(move_line.analytic_account_id.id) for move_line in self}
+ return mapping
def _sale_prepare_sale_line_values(self, order, price):
""" Generate the sale.line creation value from the current move line """
diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py
index fb688744065..49a7b803315 100644
--- a/addons/sale/models/sale_order.py
+++ b/addons/sale/models/sale_order.py
@@ -250,7 +250,6 @@ class SaleOrder(models.Model):
analytic_account_id = fields.Many2one(
comodel_name='account.analytic.account',
string="Analytic Account",
- compute='_compute_analytic_account_id', store=True, readonly=False,
copy=False, check_company=True, # Unrequired company
states=READONLY_FIELD_STATES,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
@@ -554,18 +553,6 @@ class SaleOrder(models.Model):
else:
record.tax_country_id = record.company_id.account_fiscal_country_id
- @api.depends('partner_id', 'date_order')
- def _compute_analytic_account_id(self):
- for order in self:
- if not order.analytic_account_id:
- default_analytic_account = order.env['account.analytic.default'].sudo().account_get(
- partner_id=order.partner_id.id,
- user_id=order.env.uid,
- date=order.date_order,
- company_id=order.company_id.id,
- )
- order.analytic_account_id = default_analytic_account.analytic_id
-
@api.depends('company_id', 'partner_id', 'amount_total')
def _compute_partner_credit_warning(self):
for order in self:
@@ -1391,10 +1378,17 @@ class SaleOrder(models.Model):
name = self.name
if prefix:
name = prefix + ": " + self.name
+ plan = self.env['account.analytic.plan'].search(['|', ('company_id', '=', self.company_id.id), ('company_id', '=', False)], limit=1)
+ if not plan:
+ plan = self.env['account.analytic.plan'].create({
+ 'name': 'Default',
+ 'company_id': self.company_id.id
+ })
return {
'name': name,
'code': self.client_order_ref,
'company_id': self.company_id.id,
+ 'plan_id': plan.id,
'partner_id': self.partner_id.id
}
diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py
index 38190cc1bab..9136d2c7e70 100644
--- a/addons/sale/models/sale_order_line.py
+++ b/addons/sale/models/sale_order_line.py
@@ -13,6 +13,7 @@ from odoo.tools import float_is_zero, float_compare, float_round
class SaleOrderLine(models.Model):
_name = 'sale.order.line'
+ _inherit = 'analytic.mixin'
_description = "Sales Order Line"
_rec_names_search = ['name', 'order_id.name']
_order = 'order_id, sequence, id'
@@ -217,11 +218,6 @@ class SaleOrderLine(models.Model):
digits='Product Unit of Measure',
store=True)
- analytic_tag_ids = fields.Many2many(
- comodel_name='account.analytic.tag',
- string="Analytic Tags",
- compute='_compute_analytic_tag_ids', store=True, readonly=False,
- domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
analytic_line_ids = fields.One2many(
comodel_name='account.analytic.line', inverse_name='so_line',
string="Analytic lines")
@@ -879,18 +875,19 @@ class SaleOrderLine(models.Model):
line.untaxed_amount_to_invoice = amount_to_invoice
- @api.depends('product_id', 'order_id.date_order', 'order_id.partner_id')
- def _compute_analytic_tag_ids(self):
+ @api.depends('order_id.partner_id', 'product_id')
+ def _compute_analytic_distribution_stored_char(self):
for line in self:
if not line.display_type and line.state == 'draft':
- default_analytic_account = line.env['account.analytic.default'].sudo().account_get(
- product_id=line.product_id.id,
- partner_id=line.order_id.partner_id.id,
- user_id=self.env.uid,
- date=line.order_id.date_order,
- company_id=line.company_id.id,
- )
- line.analytic_tag_ids = default_analytic_account.analytic_tag_ids
+ distribution = line.env['account.analytic.distribution.model']._get_distributionjson({
+ "product_id": line.product_id.id,
+ "product_categ_id": line.product_id.categ_id.id,
+ "partner_id": line.order_id.partner_id.id,
+ "partner_category_id": line.order_id.partner_id.category_id.ids,
+ "company_id": line.company_id.id,
+ })
+ line.analytic_distribution_stored_char = distribution or line.analytic_distribution_stored_char
+ line._compute_analytic_distribution()
@api.depends('product_id', 'state', 'qty_invoiced', 'qty_delivered')
def _compute_product_updatable(self):
@@ -1000,7 +997,7 @@ class SaleOrderLine(models.Model):
def _get_protected_fields(self):
return [
'product_id', 'name', 'price_unit', 'product_uom', 'product_uom_qty',
- 'tax_id', 'analytic_tag_ids'
+ 'tax_id', 'analytic_distribution_stored_char'
]
def _update_line_quantity(self, values):
@@ -1077,12 +1074,17 @@ class SaleOrderLine(models.Model):
'discount': self.discount,
'price_unit': self.price_unit,
'tax_ids': [Command.set(self.tax_id.ids)],
- 'analytic_tag_ids': [Command.set(self.analytic_tag_ids.ids)],
+ 'analytic_distribution': self.analytic_distribution,
'sale_line_ids': [Command.link(self.id)],
'is_downpayment': self.is_downpayment,
}
- if self.order_id.analytic_account_id:
- res['analytic_account_id'] = self.order_id.analytic_account_id.id
+ analytic_account_id = self.order_id.analytic_account_id.id
+ if analytic_account_id:
+ res['analytic_distribution'] = res['analytic_distribution'] or {}
+ if self.analytic_distribution:
+ res['analytic_distribution'][analytic_account_id] = self.analytic_distribution.get(analytic_account_id, 0) + 100
+ else:
+ res['analytic_distribution'][analytic_account_id] = 100
if optional_values:
res.update(optional_values)
if self.display_type:
diff --git a/addons/sale/security/ir.model.access.csv b/addons/sale/security/ir.model.access.csv
index 83041148a3b..ec6bbc89646 100644
--- a/addons/sale/security/ir.model.access.csv
+++ b/addons/sale/security/ir.model.access.csv
@@ -1,7 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_account_salesman,account_account salesman,account.model_account_account,sales_team.group_sale_salesman,1,0,0,0
access_account_analytic_account_salesman,account_analytic_account salesman,analytic.model_account_analytic_account,sales_team.group_sale_salesman,1,1,1,0
-access_account_analytic_tag_sale_salesman,account.analytic.tag.sale.salesman,analytic.model_account_analytic_tag,sales_team.group_sale_salesman,1,0,0,0
access_account_account_tag_sale_salesman,account.account.tag.sale.salesman,account.model_account_account_tag,sales_team.group_sale_salesman,1,0,0,0
access_account_invoice_send_salesman,access.account.invoice.send.salesman,account.model_account_invoice_send,sales_team.group_sale_salesman,1,1,1,0
access_sale_account_journal,account.journal sale order.user,account.model_account_journal,sales_team.group_sale_salesman,1,0,0,0
diff --git a/addons/sale/tests/test_reinvoice.py b/addons/sale/tests/test_reinvoice.py
index 6b893d46680..9669a7aa3ba 100644
--- a/addons/sale/tests/test_reinvoice.py
+++ b/addons/sale/tests/test_reinvoice.py
@@ -12,10 +12,16 @@ class TestReInvoice(TestSaleCommon):
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan',
+ 'company_id': cls.partner_a.company_id.id,
+ })
+
cls.analytic_account = cls.env['account.analytic.account'].create({
'name': 'Test AA',
'code': 'TESTSALE_REINVOICE',
'company_id': cls.partner_a.company_id.id,
+ 'plan_id': cls.analytic_plan.id,
'partner_id': cls.partner_a.id
})
@@ -60,11 +66,11 @@ class TestReInvoice(TestSaleCommon):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_cost']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_cost']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_a = move_form.save()
invoice_a.action_post()
@@ -88,11 +94,11 @@ class TestReInvoice(TestSaleCommon):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_cost']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_cost']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_b = move_form.save()
invoice_b.action_post()
@@ -171,11 +177,11 @@ class TestReInvoice(TestSaleCommon):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_sales_price']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_sales_price']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_a = move_form.save()
invoice_a.action_post()
@@ -199,11 +205,11 @@ class TestReInvoice(TestSaleCommon):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_sales_price']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_sales_price']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_b = move_form.save()
invoice_b.action_post()
@@ -237,7 +243,7 @@ class TestReInvoice(TestSaleCommon):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_no']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_a = move_form.save()
invoice_a.action_post()
diff --git a/addons/sale/tests/test_sale_flow.py b/addons/sale/tests/test_sale_flow.py
index 3095b5345da..f320d53b60f 100644
--- a/addons/sale/tests/test_sale_flow.py
+++ b/addons/sale/tests/test_sale_flow.py
@@ -31,9 +31,15 @@ class TestSaleFlow(TestSaleCommonBase):
'company_id': False,
})
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan',
+ 'company_id': cls.company.id,
+ })
+
cls.analytic_account = cls.env['account.analytic.account'].create({
'name': 'Test analytic_account',
'code': 'analytic_account',
+ 'plan_id': cls.analytic_plan.id,
'company_id': cls.company.id,
'partner_id': cls.partner_a.id
})
diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py
index dbc7351a857..3de988b19cd 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -374,27 +374,23 @@ class TestSalesTeam(SaleCommon):
sale_order.user_id = self.user_not_in_team
self.assertEqual(sale_order.team_id.id, self.sale_team_2.id, 'Should not reset the team to default')
- def test_sale_order_analytic_tag_change(self):
+ def test_sale_order_analytic_distribution_change(self):
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
- self.env.user.groups_id += self.env.ref('analytic.group_analytic_tags')
- analytic_account_super = self.env['account.analytic.account'].create({'name': 'Super Account'})
- analytic_account_great = self.env['account.analytic.account'].create({'name': 'Great Account'})
- analytic_tag_super = self.env['account.analytic.tag'].create({'name': 'Super Tag'})
- analytic_tag_great = self.env['account.analytic.tag'].create({'name': 'Great Tag'})
+ analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
+ analytic_account_super = self.env['account.analytic.account'].create({'name': 'Super Account', 'plan_id': analytic_plan.id})
+ analytic_account_great = self.env['account.analytic.account'].create({'name': 'Great Account', 'plan_id': analytic_plan.id})
super_product = self.env['product.product'].create({'name': 'Super Product'})
great_product = self.env['product.product'].create({'name': 'Great Product'})
product_no_account = self.env['product.product'].create({'name': 'Product No Account'})
- self.env['account.analytic.default'].create([
+ self.env['account.analytic.distribution.model'].create([
{
- 'analytic_id': analytic_account_super.id,
+ 'analytic_distribution': {analytic_account_super.id: 100},
'product_id': super_product.id,
- 'analytic_tag_ids': [analytic_tag_super.id],
},
{
- 'analytic_id': analytic_account_great.id,
+ 'analytic_distribution': {analytic_account_great.id: 100},
'product_id': great_product.id,
- 'analytic_tag_ids': [analytic_tag_great.id],
},
])
sale_order = self.env['sale.order'].create({
@@ -406,11 +402,9 @@ class TestSalesTeam(SaleCommon):
'order_id': sale_order.id,
})
- self.assertEqual(sol.analytic_tag_ids.id, analytic_tag_super.id, "The analytic tag should be set to 'Super Tag'")
+ self.assertEqual(sol.analytic_distribution, {analytic_account_super.id: 100}, "The analytic distribution should be set to Super Account")
sol.write({'product_id': great_product.id})
- self.assertEqual(sol.analytic_tag_ids.id, analytic_tag_great.id, "The analytic tag should be set to 'Great Tag'")
- sol.write({'product_id': product_no_account.id})
- self.assertFalse(sol.analytic_tag_ids.id, "The analytic account should not be set")
+ self.assertEqual(sol.analytic_distribution, {analytic_account_great.id: 100}, "The analytic distribution should be set to Great Account")
so_no_analytic_account = self.env['sale.order'].create({
'partner_id': self.env.ref('base.res_partner_1').id,
@@ -419,7 +413,7 @@ class TestSalesTeam(SaleCommon):
'name': super_product.name,
'product_id': super_product.id,
'order_id': so_no_analytic_account.id,
- 'analytic_tag_ids': False,
+ 'analytic_distribution': False,
})
so_no_analytic_account.action_confirm()
- self.assertFalse(sol_no_analytic_account.analytic_tag_ids.id, "The compute should not overwrite what the user has set.")
+ self.assertFalse(sol_no_analytic_account.analytic_distribution, "The compute should not overwrite what the user has set.")
diff --git a/addons/sale/tests/test_sale_to_invoice.py b/addons/sale/tests/test_sale_to_invoice.py
index be7c6d2d29b..2b65c54f52a 100644
--- a/addons/sale/tests/test_sale_to_invoice.py
+++ b/addons/sale/tests/test_sale_to_invoice.py
@@ -414,14 +414,15 @@ class TestSaleToInvoice(TestSaleCommon):
downpayment.create_invoices()
self.assertEqual(so_for_downpayment.invoice_ids[0].company_id.id, so_company_id, "The company of the downpayment invoice should be the same as the one from the SO")
- def test_invoice_analytic_account_default(self):
+ def test_invoice_analytic_distribution_model(self):
""" Tests whether, when an analytic account rule is set and the so has no analytic account,
- the default analytic acount is correctly computed in the invoice.
+ the default analytic account is correctly computed in the invoice.
"""
- analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'})
+ analytic_plan_default = self.env['account.analytic.plan'].create({'name': 'default'})
+ analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan_default.id})
- self.env['account.analytic.default'].create({
- 'analytic_id': analytic_account_default.id,
+ self.env['account.analytic.distribution.model'].create({
+ 'analytic_distribution': {analytic_account_default.id: 100},
'product_id': self.product_a.id,
})
@@ -446,7 +447,7 @@ class TestSaleToInvoice(TestSaleCommon):
down_payment.create_invoices()
aml = self.env['account.move.line'].search([('move_id', 'in', so.invoice_ids.ids)])[0]
- self.assertRecordValues(aml, [{'analytic_account_id': analytic_account_default.id}])
+ self.assertRecordValues(aml, [{'analytic_distribution': {analytic_account_default.id: 100}}])
def test_invoice_analytic_account_so_not_default(self):
""" Tests whether, when an analytic account rule is set and the so has an analytic account,
@@ -454,11 +455,12 @@ class TestSaleToInvoice(TestSaleCommon):
"""
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
- analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'})
- analytic_account_so = self.env['account.analytic.account'].create({'name': 'so'})
+ analytic_plan_default = self.env['account.analytic.plan'].create({'name': 'default'})
+ analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan_default.id})
+ analytic_account_so = self.env['account.analytic.account'].create({'name': 'so', 'plan_id': analytic_plan_default.id})
- self.env['account.analytic.default'].create({
- 'analytic_id': analytic_account_default.id,
+ self.env['account.analytic.distribution.model'].create({
+ 'analytic_distribution': {analytic_account_default.id: 100},
'product_id': self.product_a.id,
})
@@ -484,7 +486,7 @@ class TestSaleToInvoice(TestSaleCommon):
down_payment.create_invoices()
aml = self.env['account.move.line'].search([('move_id', 'in', so.invoice_ids.ids)])[0]
- self.assertRecordValues(aml, [{'analytic_account_id': analytic_account_so.id}])
+ self.assertRecordValues(aml, [{'analytic_distribution': {analytic_account_default.id: 100, analytic_account_so.id: 100}}])
def test_invoice_after_product_return_price_not_default(self):
so = self.env['sale.order'].create({
@@ -598,7 +600,7 @@ class TestSaleToInvoice(TestSaleCommon):
'product_uom_id': serv_cost.uom_id.id,
'quantity': 2,
'price_unit': serv_cost.standard_price,
- 'analytic_account_id': so.analytic_account_id.id,
+ 'analytic_distribution': {so.analytic_account_id.id: 100},
}),
],
})
diff --git a/addons/sale/views/sale_order_views.xml b/addons/sale/views/sale_order_views.xml
index 1d3603f31f5..d74a626e4a5 100644
--- a/addons/sale/views/sale_order_views.xml
+++ b/addons/sale/views/sale_order_views.xml
@@ -362,7 +362,12 @@
days
-
+
+
+
@@ -430,14 +435,13 @@
domain="[('sale_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"
widget="product_configurator"/>
-
+
+
+
30
-
+
-
+
-
+
diff --git a/addons/sale_timesheet/data/sale_service_demo.xml b/addons/sale_timesheet/data/sale_service_demo.xml
index f8804488898..b88ad588615 100644
--- a/addons/sale_timesheet/data/sale_service_demo.xml
+++ b/addons/sale_timesheet/data/sale_service_demo.xml
@@ -24,6 +24,7 @@
After-Sales Services
INT
+
diff --git a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
index 981d8a10c9a..4fe1bbf68c0 100644
--- a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
+++ b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
@@ -160,7 +160,7 @@ tour.register('sale_timesheet_tour', {
content: 'Select a Sales Order Item as Default Sales Order Item for each task in this project.',
run: 'text S',
}, {
- trigger: 'ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
+ trigger: '[name="sale_line_id"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
content: 'Select the Sales Order Item in the autocomplete dropdown.',
}, {
trigger: 'div[name="sale_line_employee_ids"] td.o_field_x2many_list_row_add > a[role="button"]',
@@ -170,7 +170,7 @@ tour.register('sale_timesheet_tour', {
content: 'Select an employee to link a Sales Order Item on his timesheets into this project.',
run: 'click',
}, {
- trigger: 'ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
+ trigger: '[name="employee_id"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
content: 'Select the first employee in the autocomplete dropdown',
}, {
trigger: 'div[name="sale_line_employee_ids"] div[name="sale_line_id"] input',
@@ -178,7 +178,7 @@ tour.register('sale_timesheet_tour', {
position: 'bottom',
run: 'text S',
}, {
- trigger: 'ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
+ trigger: '[name=sale_line_id] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
content: 'Select the first Sales Order Item in the autocomplete dropdown.',
}, {
trigger: 'h1 > div[name="name"] > input',
diff --git a/addons/sale_timesheet/tests/common.py b/addons/sale_timesheet/tests/common.py
index 3bee18fbce5..5d30672d5de 100644
--- a/addons/sale_timesheet/tests/common.py
+++ b/addons/sale_timesheet/tests/common.py
@@ -55,9 +55,14 @@ class TestCommonSaleTimesheet(TestSaleProjectCommon):
# Account and project
cls.analytic_account_sale.name = 'Project for selling timesheet - AA'
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan Test',
+ 'company_id': cls.company_data_2['company'].id,
+ })
cls.analytic_account_sale_company_B = cls.env['account.analytic.account'].create({
'name': 'Project for selling timesheet Company B - AA',
'code': 'AA-2030',
+ 'plan_id': cls.analytic_plan.id,
'company_id': cls.company_data_2['company'].id,
})
diff --git a/addons/sale_timesheet/tests/test_reinvoice.py b/addons/sale_timesheet/tests/test_reinvoice.py
index 9c78639d6d3..471b84b9b4f 100644
--- a/addons/sale_timesheet/tests/test_reinvoice.py
+++ b/addons/sale_timesheet/tests/test_reinvoice.py
@@ -28,10 +28,16 @@ class TestReInvoice(TestCommonSaleTimesheet):
cls.company_data['product_order_no'].write(service_values)
# create AA, SO and invoices
+ cls.analytic_plan = cls.env['account.analytic.plan'].create({
+ 'name': 'Plan',
+ 'company_id': cls.company_data['company'].id,
+ })
+
cls.analytic_account = cls.env['account.analytic.account'].create({
'name': 'Test AA',
'code': 'TESTSALE_TIMESHEET_REINVOICE',
'company_id': cls.company_data['company'].id,
+ 'plan_id': cls.analytic_plan.id,
'partner_id': cls.partner_a.id
})
@@ -87,11 +93,11 @@ class TestReInvoice(TestCommonSaleTimesheet):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_cost']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_cost']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_a = move_form.save()
invoice_a.action_post()
@@ -122,11 +128,11 @@ class TestReInvoice(TestCommonSaleTimesheet):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_cost']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_cost']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_b = move_form.save()
invoice_b.action_post()
@@ -179,11 +185,11 @@ class TestReInvoice(TestCommonSaleTimesheet):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_sales_price']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_sales_price']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_a = move_form.save()
invoice_a.action_post()
@@ -214,11 +220,11 @@ class TestReInvoice(TestCommonSaleTimesheet):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_delivery_sales_price']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_sales_price']
line_form.quantity = 2.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_b = move_form.save()
invoice_b.action_post()
@@ -252,7 +258,7 @@ class TestReInvoice(TestCommonSaleTimesheet):
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.company_data['product_order_no']
line_form.quantity = 3.0
- line_form.analytic_account_id = self.analytic_account
+ line_form.analytic_distribution = {self.analytic_account.id: 100}
invoice_a = move_form.save()
invoice_a.action_post()
diff --git a/addons/sale_timesheet/tests/test_sale_service.py b/addons/sale_timesheet/tests/test_sale_service.py
index 962f7fefcc6..afd91b59d76 100644
--- a/addons/sale_timesheet/tests/test_sale_service.py
+++ b/addons/sale_timesheet/tests/test_sale_service.py
@@ -685,8 +685,8 @@ class TestSaleService(TestCommonSaleTimesheet):
# Check that the resulting invoice line and the project have the same analytic account
invoice_line = self.sale_order.invoice_ids.line_ids.filtered(lambda line: line.product_id == product_add)
- self.assertEqual(invoice_line.analytic_account_id, self.project_global.analytic_account_id,
- "SOL's analytic account should be the same as the project's")
+ self.assertEqual(invoice_line.analytic_distribution, {self.project_global.analytic_account_id.id: 100},
+ "SOL's analytic distribution should contain the project analytic account")
def test_sale_timesheet_invoice(self):
""" Test timesheet is correctly linked to an invoice when its SOL is invoiced
diff --git a/addons/stock_account/models/account_move.py b/addons/stock_account/models/account_move.py
index 4d8d332c646..65e8c3ad7ec 100644
--- a/addons/stock_account/models/account_move.py
+++ b/addons/stock_account/models/account_move.py
@@ -177,8 +177,7 @@ class AccountMove(models.Model):
'price_unit': -price_unit,
'amount_currency': amount_currency,
'account_id': credit_expense_account.id,
- 'analytic_account_id': line.analytic_account_id.id,
- 'analytic_tag_ids': [(6, 0, line.analytic_tag_ids.ids)],
+ 'analytic_distribution': line.analytic_distribution,
'display_type': 'cogs',
'tax_ids': [],
})
diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py
index ac9ead92817..f08fbcbd0d2 100644
--- a/addons/stock_account/models/stock_move.py
+++ b/addons/stock_account/models/stock_move.py
@@ -393,7 +393,7 @@ class StockMove(models.Model):
return res
- def _prepare_analytic_line(self):
+ def _prepare_analytic_lines(self):
self.ensure_one()
if not self._get_analytic_account():
return False
@@ -411,9 +411,9 @@ class StockMove(models.Model):
accounts_data = self.product_id.product_tmpl_id.get_product_accounts()
account_valuation = accounts_data.get('stock_valuation', False)
analytic_line_vals = self.stock_valuation_layer_ids.account_move_id.line_ids.filtered(
- lambda l: l.account_id == account_valuation)._prepare_analytic_line()
- amount = - sum(vals['amount'] for vals in analytic_line_vals)
- unit_amount = - sum(vals['unit_amount'] for vals in analytic_line_vals)
+ lambda l: l.account_id == account_valuation)._prepare_analytic_lines()
+ amount = - sum(sum(vals['amount'] for vals in lists) for lists in analytic_line_vals)
+ unit_amount = - sum(sum(vals['unit_amount'] for vals in lists) for lists in analytic_line_vals)
elif sum(self.stock_valuation_layer_ids.mapped('quantity')):
amount = sum(self.stock_valuation_layer_ids.mapped('value'))
unit_amount = - sum(self.stock_valuation_layer_ids.mapped('quantity'))
@@ -520,7 +520,7 @@ class StockMove(models.Model):
analytic_lines_vals = []
moves_to_link = []
for move in self:
- analytic_line_vals = move._prepare_analytic_line()
+ analytic_line_vals = move._prepare_analytic_lines()
if not analytic_line_vals:
continue
moves_to_link.append(move.id)
diff --git a/addons/web/static/tests/legacy/core/py_utils_tests.js b/addons/web/static/tests/legacy/core/py_utils_tests.js
index e3cec9ede9c..a82fea85f69 100644
--- a/addons/web/static/tests/legacy/core/py_utils_tests.js
+++ b/addons/web/static/tests/legacy/core/py_utils_tests.js
@@ -941,7 +941,7 @@ QUnit.module('core', function () {
"price_unit": 100,
"account_id": 853,
"discount": 0,
- "account_analytic_id": false,
+ "analytic_distribution": false,
"company_id": false,
"note": false,
"invoice_line_tax_ids": [[6, false, [1]]],
@@ -998,7 +998,7 @@ QUnit.module('core', function () {
[0, false, {
account_id: 55,
amount_currency: 0,
- analytic_account_id: false,
+ analytic_distribution: false,
credit: 0,
currency_id: false,
date_maturity: false,
@@ -1025,7 +1025,7 @@ QUnit.module('core', function () {
line_id: [[0, false, {
account_id: 55,
amount_currency: 0,
- analytic_account_id: false,
+ analytic_distribution: false,
credit: 0,
currency_id: false,
date_maturity: false,