[IMP] {,mrp_,stock_}account, *: add default stock accounts and production cost

account

1. We introduced a new Production Cost account that can be set on
locations with Production type. When products move into/out of a
production location. The account entry will be post on this account
instead of previous Stock in/out account.

2. On the accounting setting page, we can now set the default values for
all stock accounts. Users can also disable automatically accounting for
stock there.

Task-3046333

closes odoo/odoo#113973

Related: odoo/enterprise#37653
Signed-off-by: Tiffany Chang <tic@odoo.com>
This commit is contained in:
yhu-odoo
2023-05-10 15:19:32 +02:00
parent bffa4377ff
commit 1eb2e7c814
20 changed files with 288 additions and 120 deletions
@@ -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"
1 id name code account_type tag_ids reconcile
3 stock_valuation Stock Valuation 1101 asset_current False
4 stock_in Stock Interim (Received) 1102 asset_current True
5 stock_out Stock Interim (Delivered) 1103 asset_current True
6 cost_of_production Cost of Production 1104 asset_current True
7 receivable Account Receivable 1210 asset_receivable True
8 to_receive_rec Products to receive 1211 asset_current True
9 tax_paid Tax Paid 1310 asset_current False
@@ -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')
+11
View File
@@ -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)
+1
View File
@@ -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',
}
+21 -1
View File
@@ -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']
+18 -17
View File
@@ -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()
+11 -65
View File
@@ -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)
@@ -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)
@@ -66,6 +66,17 @@
</field>
</record>
<record id="view_category_property_form" model="ir.ui.view">
<field name="name">product.category.stock.property.form.inherit</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="stock_account.view_category_property_form"/>
<field name="arch" type="xml">
<field name="property_stock_account_output_categ_id" position="after">
<field name="property_stock_account_production_cost_id" options="{'no_create': True}" attrs="{'required':[ ('property_valuation', '=', 'real_time')]}" />
</field>
</field>
</record>
<record id="action_compute_price_bom_template" model="ir.actions.server">
<field name="name">Compute Price from BoM</field>
<field name="model_id" ref="product.model_product_template"/>
@@ -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
@@ -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):
@@ -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},
@@ -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:
+17 -15
View File
@@ -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()
@@ -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'
@@ -12,4 +12,9 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_stock_accounting_automatic" model="res.groups">
<field name="name">Stock Accounting Automatic</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</odoo>
@@ -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',
@@ -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.
"""
+4 -1
View File
@@ -20,7 +20,10 @@
<group name="logistics" position="after">
<group string="Inventory Valuation">
<field name="property_cost_method"/>
<field name="property_valuation" groups="account.group_account_readonly,stock.group_stock_manager"/>
<label for="property_valuation" groups="stock_account.group_stock_accounting_automatic"/>
<div groups="stock_account.group_stock_accounting_automatic">
<field name="property_valuation" groups="account.group_account_readonly,stock.group_stock_manager"/>
</div>
</group>
</group>
</field>
@@ -30,10 +30,12 @@
<field name="inherit_id" ref="stock.view_location_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='additional_info']" position="after">
<group string="Accounting Information" attrs="{'invisible':[('usage','not in',('inventory','production'))]}">
<field name="valuation_in_account_id" options="{'no_create': True}"/>
<field name="valuation_out_account_id" options="{'no_create': True}"/>
</group>
<div groups="stock_account.group_stock_accounting_automatic">
<group string="Accounting Information" attrs="{'invisible':[('usage','not in',('inventory','production'))]}">
<field name="valuation_in_account_id" options="{'no_create': True}"/>
<field name="valuation_out_account_id" options="{'no_create': True}"/>
</group>
</div>
</xpath>
</field>
</record>