[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:
@@ -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"
|
||||
|
||||
|
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user