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 @@ - - - - +
+ + + + +