diff --git a/addons/account/data/template/account.account-generic_coa.csv b/addons/account/data/template/account.account-generic_coa.csv
index 652f679da90..665ec9dd1fc 100644
--- a/addons/account/data/template/account.account-generic_coa.csv
+++ b/addons/account/data/template/account.account-generic_coa.csv
@@ -3,6 +3,7 @@
"stock_valuation","Stock Valuation","1101","asset_current","","False"
"stock_in","Stock Interim (Received)","1102","asset_current","","True"
"stock_out","Stock Interim (Delivered)","1103","asset_current","","True"
+"cost_of_production","Cost of Production","1104","asset_current","","True"
"receivable","Account Receivable","1210","asset_receivable","","True"
"to_receive_rec","Products to receive","1211","asset_current","","True"
"tax_paid","Tax Paid","1310","asset_current","","False"
diff --git a/addons/account/models/template_generic_coa.py b/addons/account/models/template_generic_coa.py
index fbdcecf7f31..b669c5868e6 100644
--- a/addons/account/models/template_generic_coa.py
+++ b/addons/account/models/template_generic_coa.py
@@ -33,6 +33,7 @@ class AccountChartTemplate(models.AbstractModel):
'property_stock_account_input_categ_id': 'stock_in',
'property_stock_account_output_categ_id': 'stock_out',
'property_stock_valuation_account_id': 'stock_valuation',
+ 'property_stock_account_production_cost_id': 'cost_of_production',
}
@template('generic_coa', 'res.company')
diff --git a/addons/mrp_account/__init__.py b/addons/mrp_account/__init__.py
index be9f4fab00a..274ebd30be4 100644
--- a/addons/mrp_account/__init__.py
+++ b/addons/mrp_account/__init__.py
@@ -1,3 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
+
+
+def _configure_journals(env):
+ # if we already have a coa installed, create journal and set property field
+ for company in env['res.company'].search([('chart_template', '!=', False)]):
+ ChartTemplate = env['account.chart.template'].with_company(company)
+ template_code = company.chart_template
+ template_data = ChartTemplate._get_chart_template_data(template_code)['template_data']
+ if 'property_stock_account_production_cost_id' in template_data:
+ data = {'property_stock_account_production_cost_id': template_data['property_stock_account_production_cost_id']}
+ ChartTemplate._post_load_data(template_code, company, data)
diff --git a/addons/mrp_account/__manifest__.py b/addons/mrp_account/__manifest__.py
index e1531fbe988..d7bd3eafddb 100644
--- a/addons/mrp_account/__manifest__.py
+++ b/addons/mrp_account/__manifest__.py
@@ -31,5 +31,6 @@ If the automated inventory valuation is active, the necessary accounting entries
],
'installable': True,
'auto_install': True,
+ 'post_init_hook': '_configure_journals',
'license': 'LGPL-3',
}
diff --git a/addons/mrp_account/models/product.py b/addons/mrp_account/models/product.py
index e10272b02c9..9e97b03c940 100644
--- a/addons/mrp_account/models/product.py
+++ b/addons/mrp_account/models/product.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import models
+from odoo import fields, models
from odoo.tools import float_round, groupby
@@ -9,6 +9,13 @@ class ProductTemplate(models.Model):
_name = 'product.template'
_inherit = 'product.template'
+ def _get_product_accounts(self):
+ accounts = super()._get_product_accounts()
+ accounts.update({
+ 'production': self.categ_id.property_stock_account_production_cost_id or False,
+ })
+ return accounts
+
def action_bom_cost(self):
templates = self.filtered(lambda t: t.product_variant_count == 1 and t.bom_count > 0)
if templates:
@@ -105,3 +112,16 @@ class ProductProduct(models.Model):
if byproduct_cost_share:
total *= float_round(1 - byproduct_cost_share / 100, precision_rounding=0.0001)
return bom.product_uom_id._compute_price(total / bom.product_qty, self.uom_id)
+
+
+class ProductCategory(models.Model):
+ _inherit = 'product.category'
+
+ property_stock_account_production_cost_id = fields.Many2one(
+ 'account.account', 'Production Account', company_dependent=True,
+ domain="[('company_id', '=', allowed_company_ids[0]), ('deprecated', '=', False)]", check_company=True,
+ help="""This account will be used as a valuation counterpart for both components and final products for manufacturing orders.
+ If there are any workcenter/employee costs, this value will remain on the account once the production is completed.""")
+
+ def _get_stock_account_property_field_names(self):
+ return super()._get_stock_account_property_field_names() + ['property_stock_account_production_cost_id']
diff --git a/addons/mrp_account/models/stock_move.py b/addons/mrp_account/models/stock_move.py
index b60db11414a..e13ff82be58 100644
--- a/addons/mrp_account/models/stock_move.py
+++ b/addons/mrp_account/models/stock_move.py
@@ -26,22 +26,23 @@ class StockMove(models.Model):
return account
return super()._get_analytic_account()
- def _get_src_account(self, accounts_data):
- if not self.unbuild_id:
- return super()._get_src_account(accounts_data)
- else:
- return self.location_dest_id.valuation_out_account_id.id or accounts_data['stock_input'].id
-
- def _get_dest_account(self, accounts_data):
- if not self.unbuild_id:
- return super()._get_dest_account(accounts_data)
- else:
- return self.location_id.valuation_in_account_id.id or accounts_data['stock_output'].id
-
- def _is_returned(self, valued_type):
- if self.unbuild_id:
- return True
- return super()._is_returned(valued_type)
-
def _ignore_automatic_valuation(self):
return bool(self.raw_material_production_id)
+
+ def _get_src_account(self, accounts_data):
+ if self._is_production():
+ return self.location_id.valuation_out_account_id.id or accounts_data['production'].id
+ return super()._get_src_account(accounts_data)
+
+ def _get_dest_account(self, accounts_data):
+ if self._is_production_consumed():
+ return self.location_dest_id.valuation_in_account_id.id or accounts_data['production'].id
+ return super()._get_dest_account(accounts_data)
+
+ def _is_production(self):
+ self.ensure_one()
+ return self.location_id.usage == 'production' and self.location_dest_id._should_be_valued()
+
+ def _is_production_consumed(self):
+ self.ensure_one()
+ return self.location_dest_id.usage == 'production' and self.location_id._should_be_valued()
diff --git a/addons/mrp_account/tests/test_mrp_account.py b/addons/mrp_account/tests/test_mrp_account.py
index 799d9aa5cba..12760cc30f9 100644
--- a/addons/mrp_account/tests/test_mrp_account.py
+++ b/addons/mrp_account/tests/test_mrp_account.py
@@ -195,6 +195,13 @@ class TestMrpAccountMove(TestAccountMoveStockCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
+ cls.production_account = cls.env['account.account'].create({
+ 'name': 'Cost of Production',
+ 'code': 'ProductionCost',
+ 'account_type': 'liability_current',
+ 'reconcile': True,
+ })
+ cls.auto_categ.property_stock_account_production_cost_id = cls.production_account
cls.product_B = cls.env["product.product"].create(
{
"name": "Product B",
@@ -238,11 +245,11 @@ class TestMrpAccountMove(TestAccountMoveStockCommon):
productA_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product A'), ('credit', '=', 0)])
productA_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product A'), ('debit', '=', 0)])
self.assertEqual(productA_debit_line.account_id, self.stock_valuation_account)
- self.assertEqual(productA_credit_line.account_id, self.stock_input_account)
+ self.assertEqual(productA_credit_line.account_id, self.production_account)
# component move
productB_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product B'), ('credit', '=', 0)])
productB_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product B'), ('debit', '=', 0)])
- self.assertEqual(productB_debit_line.account_id, self.stock_output_account)
+ self.assertEqual(productB_debit_line.account_id, self.production_account)
self.assertEqual(productB_credit_line.account_id, self.stock_valuation_account)
# unbuild
@@ -253,71 +260,10 @@ class TestMrpAccountMove(TestAccountMoveStockCommon):
# finished product move
productA_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product A'), ('credit', '=', 0)])
productA_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product A'), ('debit', '=', 0)])
- self.assertEqual(productA_debit_line.account_id, self.stock_input_account)
+ self.assertEqual(productA_debit_line.account_id, self.production_account)
self.assertEqual(productA_credit_line.account_id, self.stock_valuation_account)
# component move
productB_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product B'), ('credit', '=', 0)])
productB_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product B'), ('debit', '=', 0)])
self.assertEqual(productB_debit_line.account_id, self.stock_valuation_account)
- self.assertEqual(productB_credit_line.account_id, self.stock_output_account)
-
- def test_unbuild_account_01(self):
- """Test when production location has its valuation accounts. After unbuild,
- the journal entries are the reversal of the journal entries created when
- produce the product.
- """
- # set accounts for production location
- production_location = self.product_A.property_stock_production
- wip_incoming_account = self.env['account.account'].create({
- 'name': 'wip incoming',
- 'code': '000001',
- 'account_type': 'asset_current',
- })
- wip_outgoing_account = self.env['account.account'].create({
- 'name': 'wip outgoing',
- 'code': '000002',
- 'account_type': 'asset_current',
- })
- production_location.write({
- 'valuation_in_account_id': wip_incoming_account.id,
- 'valuation_out_account_id': wip_outgoing_account.id,
- })
-
- # build
- production_form = Form(self.env['mrp.production'])
- production_form.product_id = self.product_A
- production_form.bom_id = self.bom
- production_form.product_qty = 1
- production = production_form.save()
- production.action_confirm()
- mo_form = Form(production)
- mo_form.qty_producing = 1
- production = mo_form.save()
- production._post_inventory()
- production.button_mark_done()
-
- # finished product move
- productA_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product A'), ('credit', '=', 0)])
- productA_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product A'), ('debit', '=', 0)])
- self.assertEqual(productA_debit_line.account_id, self.stock_valuation_account)
- self.assertEqual(productA_credit_line.account_id, wip_outgoing_account)
- # component move
- productB_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product B'), ('credit', '=', 0)])
- productB_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'MO%Product B'), ('debit', '=', 0)])
- self.assertEqual(productB_debit_line.account_id, wip_incoming_account)
- self.assertEqual(productB_credit_line.account_id, self.stock_valuation_account)
-
- # unbuild
- res_dict = production.button_unbuild()
- wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save()
- wizard.action_validate()
-
- productA_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product A'), ('credit', '=', 0)])
- productA_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product A'), ('debit', '=', 0)])
- self.assertEqual(productA_debit_line.account_id, wip_outgoing_account)
- self.assertEqual(productA_credit_line.account_id, self.stock_valuation_account)
- # component move
- productB_debit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product B'), ('credit', '=', 0)])
- productB_credit_line = self.env['account.move.line'].search([('ref', 'ilike', 'UB%Product B'), ('debit', '=', 0)])
- self.assertEqual(productB_debit_line.account_id, self.stock_valuation_account)
- self.assertEqual(productB_credit_line.account_id, wip_incoming_account)
+ self.assertEqual(productB_credit_line.account_id, self.production_account)
diff --git a/addons/mrp_account/tests/test_valuation_layers.py b/addons/mrp_account/tests/test_valuation_layers.py
index 097625e99a3..ea6f641d5bf 100644
--- a/addons/mrp_account/tests/test_valuation_layers.py
+++ b/addons/mrp_account/tests/test_valuation_layers.py
@@ -4,7 +4,9 @@
""" Implementation of "INVENTORY VALUATION TESTS (With valuation layers)" spreadsheet. """
from odoo.addons.stock_account.tests.test_stockvaluationlayer import TestStockValuationCommon
+from odoo.addons.stock_account.tests.test_stockvaluation import TestStockValuation
from odoo.tests import Form
+from odoo.tests.common import tagged
class TestMrpValuationCommon(TestStockValuationCommon):
@@ -348,3 +350,158 @@ class TestMrpValuationStandard(TestMrpValuationCommon):
])
self.assertEqual(self.component.qty_available, 1)
self.assertEqual(self.component.value_svl, 1424)
+
+
+@tagged("post_install", "-at_install")
+class TestMrpStockValuation(TestStockValuation):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.production_account = cls.env['account.account'].create({
+ 'name': 'Cost of Production',
+ 'code': 'ProductionCost',
+ 'account_type': 'liability_current',
+ 'reconcile': True,
+ })
+ cls.product1.categ_id.property_stock_account_production_cost_id = cls.production_account
+
+ def _get_production_cost_move_lines(self):
+ return self.env['account.move.line'].search([
+ ('account_id', '=', self.production_account.id),
+ ], order='date, id')
+
+ def test_production_account_00(self):
+ """Create move into/out of a production location, test we create account
+ entries with the Production Cost account.
+ """
+ production_location = self.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', self.env.company.id)])
+ picking_type_in = self.env.ref('stock.picking_type_in')
+ picking_type_out = self.env.ref('stock.picking_type_out')
+
+ self.product1.categ_id.property_cost_method = 'standard'
+ self.product1.standard_price = 10
+
+ # move into production location
+ production_in = self.env['stock.picking'].create({
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': production_location.id,
+ 'picking_type_id': picking_type_out.id,
+ })
+ move = self.env['stock.move'].create({
+ 'picking_id': production_in.id,
+ 'name': 'IN 10 @ 10',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': production_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 10.0,
+ })
+ production_in.action_confirm()
+ move.quantity_done = 10
+ production_in.button_validate()
+
+ in_aml = self._get_production_cost_move_lines()
+ self.assertEqual(in_aml.debit, 100)
+ self.assertEqual(in_aml.product_id, self.product1)
+
+ # move out of production location
+ production_out = self.env['stock.picking'].create({
+ 'location_id': production_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'picking_type_id': picking_type_in.id,
+ })
+ move = self.env['stock.move'].create({
+ 'picking_id': production_out.id,
+ 'name': 'OUT 10 @ 10',
+ 'location_id': production_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 10.0,
+ })
+ production_out.action_confirm()
+ move.quantity_done = 10
+ production_out.button_validate()
+
+ out_aml = self._get_production_cost_move_lines() - in_aml
+ self.assertEqual(out_aml.credit, 100)
+ self.assertEqual(in_aml.product_id, self.product1)
+
+ def test_production_account_01(self):
+ """Create move into/out of a production location with its own stock accounts
+ test we create account entries with those accounts instead of Production
+ Cost account.
+ """
+ production_out_account = self.env['account.account'].create({
+ 'name': 'Production out',
+ 'code': 'ProductionOut',
+ 'account_type': 'liability_current',
+ 'reconcile': True,
+ })
+ production_in_account = self.env['account.account'].create({
+ 'name': 'Production in',
+ 'code': 'ProductionIn',
+ 'account_type': 'liability_current',
+ 'reconcile': True,
+ })
+
+ production_location = self.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', self.env.company.id)])
+ production_location.write({
+ 'valuation_in_account_id': production_in_account.id,
+ 'valuation_out_account_id': production_out_account.id,
+ })
+ picking_type_in = self.env.ref('stock.picking_type_in')
+ picking_type_out = self.env.ref('stock.picking_type_out')
+
+ self.product1.categ_id.property_cost_method = 'standard'
+ self.product1.standard_price = 10
+
+ # move into production location
+ production_in = self.env['stock.picking'].create({
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': production_location.id,
+ 'picking_type_id': picking_type_out.id,
+ })
+ move = self.env['stock.move'].create({
+ 'picking_id': production_in.id,
+ 'name': 'IN 10 @ 10',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': production_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 10.0,
+ })
+ production_in.action_confirm()
+ move.quantity_done = 10
+ production_in.button_validate()
+
+ in_aml = self.env['account.move.line'].search([
+ ('account_id', '=', production_in_account.id),
+ ], order='date, id')
+ self.assertEqual(in_aml.debit, 100)
+ self.assertEqual(in_aml.product_id, self.product1)
+
+ # move out of production location
+ production_out = self.env['stock.picking'].create({
+ 'location_id': production_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'picking_type_id': picking_type_in.id,
+ })
+ move = self.env['stock.move'].create({
+ 'picking_id': production_out.id,
+ 'name': 'OUT 10 @ 10',
+ 'location_id': production_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 10.0,
+ })
+ production_out.action_confirm()
+ move.quantity_done = 10
+ production_out.button_validate()
+
+ out_aml = self.env['account.move.line'].search([
+ ('account_id', '=', production_out_account.id),
+ ], order='date, id')
+ self.assertEqual(out_aml.credit, 100)
+ self.assertEqual(in_aml.product_id, self.product1)
diff --git a/addons/mrp_account/views/product_views.xml b/addons/mrp_account/views/product_views.xml
index e310ff71a65..cda853fd1bb 100644
--- a/addons/mrp_account/views/product_views.xml
+++ b/addons/mrp_account/views/product_views.xml
@@ -66,6 +66,17 @@
+
+ product.category.stock.property.form.inherit
+ product.category
+
+
+
+
+
+
+
+
Compute Price from BoM
diff --git a/addons/mrp_subcontracting_account/models/stock_move.py b/addons/mrp_subcontracting_account/models/stock_move.py
index 4469e7459bf..2aaa49f795c 100644
--- a/addons/mrp_subcontracting_account/models/stock_move.py
+++ b/addons/mrp_subcontracting_account/models/stock_move.py
@@ -23,7 +23,7 @@ class StockMove(models.Model):
if not currency.is_zero(subcontract_service_cost):
del rslt['credit_line_vals']
component_cost = -credit_value + subcontract_service_cost
- component_cost_account = self.product_id.product_tmpl_id.get_product_accounts()['stock_output']
+ service_cost_account = self.product_id.product_tmpl_id.get_product_accounts()['stock_input']
rslt['subcontract_credit_line_vals'] = {
'name': description,
'product_id': self.product_id.id,
@@ -32,7 +32,7 @@ class StockMove(models.Model):
'ref': description,
'partner_id': partner_id,
'balance': -subcontract_service_cost,
- 'account_id': credit_account_id,
+ 'account_id': service_cost_account.id,
}
rslt['component_credit_line_vals'] = {
'name': description,
@@ -42,6 +42,6 @@ class StockMove(models.Model):
'ref': description,
'partner_id': partner_id,
'balance': component_cost,
- 'account_id': component_cost_account.id,
+ 'account_id': credit_account_id,
}
return rslt
diff --git a/addons/mrp_subcontracting_account/tests/test_subcontracting_account.py b/addons/mrp_subcontracting_account/tests/test_subcontracting_account.py
index dc42e38bb18..9a2dc4d7d28 100644
--- a/addons/mrp_subcontracting_account/tests/test_subcontracting_account.py
+++ b/addons/mrp_subcontracting_account/tests/test_subcontracting_account.py
@@ -22,6 +22,7 @@ class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
stock_in_acc_id = product_category_all.property_stock_account_input_categ_id.id
stock_out_acc_id = product_category_all.property_stock_account_output_categ_id.id
stock_valu_acc_id = product_category_all.property_stock_valuation_account_id.id
+ stock_cop_acc_id = product_category_all.property_stock_account_production_cost_id.id
# IN 10@10 comp1 10@20 comp2
move1 = self.env['stock.move'].create({
@@ -85,13 +86,13 @@ class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
# Receipt from subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.finished.id, 'debit': 60.0, 'credit': 0.0},
{'account_id': stock_in_acc_id, 'product_id': self.finished.id, 'debit': 0.0, 'credit': 30.0},
- {'account_id': stock_out_acc_id, 'product_id': self.finished.id, 'debit': 0.0, 'credit': 30.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.finished.id, 'debit': 0.0, 'credit': 30.0},
# Delivery com2 to subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.comp2.id, 'debit': 0.0, 'credit': 20.0},
- {'account_id': stock_out_acc_id, 'product_id': self.comp2.id, 'debit': 20.0, 'credit': 0.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.comp2.id, 'debit': 20.0, 'credit': 0.0},
# Delivery com2 to subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.comp1.id, 'debit': 0.0, 'credit': 10.0},
- {'account_id': stock_out_acc_id, 'product_id': self.comp1.id, 'debit': 10.0, 'credit': 0.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.comp1.id, 'debit': 10.0, 'credit': 0.0},
])
# Do the same without any additionnal cost
@@ -117,14 +118,14 @@ class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
amls = self.env['account.move.line'].search([('id', 'not in', all_amls_ids)])
self.assertRecordValues(amls, [
# Receipt from subcontractor
- {'account_id': stock_in_acc_id, 'product_id': self.finished.id, 'debit': 0.0, 'credit': 30.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.finished.id, 'debit': 0.0, 'credit': 30.0},
{'account_id': stock_valu_acc_id, 'product_id': self.finished.id, 'debit': 30.0, 'credit': 0.0},
# Delivery com2 to subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.comp2.id, 'debit': 0.0, 'credit': 20.0},
- {'account_id': stock_out_acc_id, 'product_id': self.comp2.id, 'debit': 20.0, 'credit': 0.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.comp2.id, 'debit': 20.0, 'credit': 0.0},
# Delivery com2 to subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.comp1.id, 'debit': 0.0, 'credit': 10.0},
- {'account_id': stock_out_acc_id, 'product_id': self.comp1.id, 'debit': 10.0, 'credit': 0.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.comp1.id, 'debit': 10.0, 'credit': 0.0},
])
def test_subcontracting_account_backorder(self):
diff --git a/addons/mrp_subcontracting_dropshipping/tests/test_anglo_saxon_valuation.py b/addons/mrp_subcontracting_dropshipping/tests/test_anglo_saxon_valuation.py
index 6c7f1bae8ae..969438c5434 100644
--- a/addons/mrp_subcontracting_dropshipping/tests/test_anglo_saxon_valuation.py
+++ b/addons/mrp_subcontracting_dropshipping/tests/test_anglo_saxon_valuation.py
@@ -75,6 +75,7 @@ class TestSubcontractingDropshippingValuation(ValuationReconciliationTestCommon)
stock_in_acc_id = self.categ_fifo_auto.property_stock_account_input_categ_id.id
stock_out_acc_id = self.categ_fifo_auto.property_stock_account_output_categ_id.id
stock_valu_acc_id = self.categ_fifo_auto.property_stock_valuation_account_id.id
+ stock_cop_acc_id = self.categ_fifo_auto.property_stock_account_production_cost_id.id
amls = self.env['account.move.line'].search([('id', 'not in', all_amls_ids)])
all_amls_ids += amls.ids
@@ -85,10 +86,10 @@ class TestSubcontractingDropshippingValuation(ValuationReconciliationTestCommon)
# Receipt from subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 220.0, 'credit': 0.0},
{'account_id': stock_in_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 200.0},
- {'account_id': stock_out_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 20.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 20.0},
# Delivery to subcontractor
{'account_id': stock_valu_acc_id, 'product_id': self.product_b.id, 'debit': 0.0, 'credit': 20.0},
- {'account_id': stock_out_acc_id, 'product_id': self.product_b.id, 'debit': 20.0, 'credit': 0.0},
+ {'account_id': stock_cop_acc_id, 'product_id': self.product_b.id, 'debit': 20.0, 'credit': 0.0},
# Initial dropshipped value
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 200.0},
{'account_id': stock_out_acc_id, 'product_id': self.product_a.id, 'debit': 200.0, 'credit': 0.0},
diff --git a/addons/stock_account/models/account_chart_template.py b/addons/stock_account/models/account_chart_template.py
index 3f0da5b87dc..e12931a7e72 100644
--- a/addons/stock_account/models/account_chart_template.py
+++ b/addons/stock_account/models/account_chart_template.py
@@ -12,11 +12,7 @@ class AccountChartTemplate(models.AbstractModel):
super()._post_load_data(template_code, company, template_data)
company = company or self.env.company
categ_values = {category.id: False for category in self.env['product.category'].search([])}
- for fname in [
- 'property_stock_account_input_categ_id',
- 'property_stock_account_output_categ_id',
- 'property_stock_valuation_account_id',
- ]:
+ for fname in self.env['product.category']._get_stock_account_property_field_names():
self.env['ir.property'].with_company(company.id)._set_multi(fname, 'product.category', categ_values, True)
value = template_data.get(fname)
if value:
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py
index 3338350ba5c..1f541dd0de7 100644
--- a/addons/stock_account/models/product.py
+++ b/addons/stock_account/models/product.py
@@ -758,22 +758,24 @@ class ProductCategory(models.Model):
domain="[('company_id', '=', allowed_company_ids[0]), ('deprecated', '=', False)]", check_company=True,
help="""When automated inventory valuation is enabled on a product, this account will hold the current value of the products.""",)
- @api.constrains('property_valuation', 'property_stock_valuation_account_id', 'property_stock_account_output_categ_id', 'property_stock_account_input_categ_id')
- def _check_valuation_accouts(self):
+ @api.model
+ def _get_stock_account_property_field_names(self):
+ return [
+ 'property_stock_account_input_categ_id',
+ 'property_stock_account_output_categ_id',
+ 'property_stock_valuation_account_id',
+ ]
+
+ @api.constrains(lambda self: tuple(self._get_stock_account_property_field_names() + ['property_valuation']))
+ def _check_valuation_accounts(self):
+ fnames = self._get_stock_account_property_field_names()
for category in self:
# "compute" properties in constraint because ORM doesn't support computed properties
- category.property_stock_account_input_categ_id = category.property_valuation == 'real_time' and (
- category.property_stock_account_input_categ_id
- or self.env['ir.property']._get('property_stock_account_input_categ_id', 'product.category')
- )
- category.property_stock_account_output_categ_id = category.property_valuation == 'real_time' and (
- category.property_stock_account_output_categ_id
- or self.env['ir.property']._get('property_stock_account_output_categ_id', 'product.category')
- )
- category.property_stock_valuation_account_id = category.property_valuation == 'real_time' and (
- category.property_stock_valuation_account_id
- or self.env['ir.property']._get('property_stock_valuation_account_id', 'product.category')
- )
+ for property_field in fnames:
+ category[property_field] = category.property_valuation == 'real_time' and (
+ category[property_field]
+ or self.env['ir.property']._get(property_field, 'product.category')
+ )
# Prevent to set the valuation account as the input or output account.
valuation_account = category.property_stock_valuation_account_id
@@ -849,4 +851,4 @@ class ProductCategory(models.Model):
@api.onchange('property_valuation')
def onchange_property_valuation(self):
# Remove or set the account stock properties if necessary
- self._check_valuation_accouts()
+ self._check_valuation_accounts()
diff --git a/addons/stock_account/models/res_config_settings.py b/addons/stock_account/models/res_config_settings.py
index dac6667ed1a..cd800362f05 100644
--- a/addons/stock_account/models/res_config_settings.py
+++ b/addons/stock_account/models/res_config_settings.py
@@ -11,3 +11,11 @@ class ResConfigSettings(models.TransientModel):
help="Affect landed costs on reception operations and split them among products to update their cost price.")
group_lot_on_invoice = fields.Boolean("Display Lots & Serial Numbers on Invoices",
implied_group='stock_account.group_lot_on_invoice')
+ group_stock_accounting_automatic = fields.Boolean(
+ "Automatic Stock Accounting", implied_group="stock_account.group_stock_accounting_automatic")
+
+ def set_values(self):
+ super().set_values()
+ if not self.group_stock_accounting_automatic:
+ self.env['product.category'].sudo().with_context(active_test=False).search([
+ ('property_valuation', '=', 'real_time')]).property_valuation = 'manual_periodic'
diff --git a/addons/stock_account/security/stock_account_security.xml b/addons/stock_account/security/stock_account_security.xml
index e641cdeb355..b56b2349d1f 100644
--- a/addons/stock_account/security/stock_account_security.xml
+++ b/addons/stock_account/security/stock_account_security.xml
@@ -12,4 +12,9 @@
+
+ Stock Accounting Automatic
+
+
+
diff --git a/addons/stock_account/tests/test_anglo_saxon_valuation_reconciliation_common.py b/addons/stock_account/tests/test_anglo_saxon_valuation_reconciliation_common.py
index dad6c0373b9..a65a49c5fff 100644
--- a/addons/stock_account/tests/test_anglo_saxon_valuation_reconciliation_common.py
+++ b/addons/stock_account/tests/test_anglo_saxon_valuation_reconciliation_common.py
@@ -18,6 +18,8 @@ class ValuationReconciliationTestCommon(AccountTestInvoicingCommon):
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
+ cls.env.user.groups_id += cls.env.ref('stock_account.group_stock_accounting_automatic')
+
cls.stock_account_product_categ = cls.env['product.category'].create({
'name': 'Test category',
'property_valuation': 'real_time',
diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py
index c1f3ee2d36b..14490258cca 100644
--- a/addons/stock_account/tests/test_stockvaluation.py
+++ b/addons/stock_account/tests/test_stockvaluation.py
@@ -104,7 +104,6 @@ class TestStockValuation(TransactionCase):
('account_id', '=', self.stock_valuation_account.id),
], order='date, id')
-
def _make_in_move(self, product, quantity, unit_cost=None):
""" Helper to create and validate a receipt move.
"""
diff --git a/addons/stock_account/views/product_views.xml b/addons/stock_account/views/product_views.xml
index 07d8098fee2..c5692886277 100644
--- a/addons/stock_account/views/product_views.xml
+++ b/addons/stock_account/views/product_views.xml
@@ -20,7 +20,10 @@
-
+
+
+
+
diff --git a/addons/stock_account/views/stock_account_views.xml b/addons/stock_account/views/stock_account_views.xml
index 34ce8bbb0f1..85fbc95ac52 100644
--- a/addons/stock_account/views/stock_account_views.xml
+++ b/addons/stock_account/views/stock_account_views.xml
@@ -30,10 +30,12 @@
-
-
-
-
+
+
+
+
+
+