[REF] stock_account: valuation layer and not admin user
Carefully sudo when necessary. task-2034044 closes odoo/odoo#34802 Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
This commit is contained in:
@@ -169,10 +169,11 @@ class ProductProduct(models.Model):
|
|||||||
for product in self:
|
for product in self:
|
||||||
if product.cost_method not in ('standard', 'average'):
|
if product.cost_method not in ('standard', 'average'):
|
||||||
continue
|
continue
|
||||||
if float_is_zero(product.quantity_svl, precision_rounding=product.uom_id.rounding):
|
quantity_svl = product.sudo().quantity_svl
|
||||||
|
if float_is_zero(quantity_svl, precision_rounding=product.uom_id.rounding):
|
||||||
continue
|
continue
|
||||||
diff = new_price - product.standard_price
|
diff = new_price - product.standard_price
|
||||||
value = company_id.currency_id.round(product.quantity_svl * diff)
|
value = company_id.currency_id.round(quantity_svl * diff)
|
||||||
if company_id.currency_id.is_zero(value):
|
if company_id.currency_id.is_zero(value):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ class ProductProduct(models.Model):
|
|||||||
'quantity': 0,
|
'quantity': 0,
|
||||||
}
|
}
|
||||||
svl_vals_list.append(svl_vals)
|
svl_vals_list.append(svl_vals)
|
||||||
stock_valuation_layers = self.env['stock.valuation.layer'].create(svl_vals_list)
|
stock_valuation_layers = self.env['stock.valuation.layer'].sudo().create(svl_vals_list)
|
||||||
|
|
||||||
# Handle account moves.
|
# Handle account moves.
|
||||||
product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self}
|
product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self}
|
||||||
@@ -233,7 +234,7 @@ class ProductProduct(models.Model):
|
|||||||
account_moves.post()
|
account_moves.post()
|
||||||
|
|
||||||
# Actually update the standard price.
|
# Actually update the standard price.
|
||||||
self.write({'standard_price': new_price})
|
self.with_context(force_company=company_id.id).sudo().write({'standard_price': new_price})
|
||||||
|
|
||||||
def _run_fifo(self, quantity, company):
|
def _run_fifo(self, quantity, company):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -291,16 +292,21 @@ class ProductProduct(models.Model):
|
|||||||
}
|
}
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
def _run_fifo_vacuum(self):
|
def _run_fifo_vacuum(self, company=None):
|
||||||
"""Compensate layer valued at an estimated price with the price of future receipts
|
"""Compensate layer valued at an estimated price with the price of future receipts
|
||||||
if any. If the estimated price is equals to the real price, no layer is created but
|
if any. If the estimated price is equals to the real price, no layer is created but
|
||||||
the original layer is marked as compensated.
|
the original layer is marked as compensated.
|
||||||
|
|
||||||
|
:param company: recordset of `res.company` to limit the execution of the vacuum
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
svls_to_vacuum = self.env['stock.valuation.layer'].search([
|
if company is None:
|
||||||
|
company = self.env.company
|
||||||
|
svls_to_vacuum = self.env['stock.valuation.layer'].sudo().search([
|
||||||
('product_id', '=', self.id),
|
('product_id', '=', self.id),
|
||||||
('remaining_qty', '<', 0),
|
('remaining_qty', '<', 0),
|
||||||
('stock_move_id', '!=', False),
|
('stock_move_id', '!=', False),
|
||||||
|
('company_id', '=', company.id),
|
||||||
])
|
])
|
||||||
for svl_to_vacuum in svls_to_vacuum:
|
for svl_to_vacuum in svls_to_vacuum:
|
||||||
domain = [
|
domain = [
|
||||||
@@ -313,7 +319,7 @@ class ProductProduct(models.Model):
|
|||||||
('create_date', '=', svl_to_vacuum.create_date),
|
('create_date', '=', svl_to_vacuum.create_date),
|
||||||
('id', '>', svl_to_vacuum.id)
|
('id', '>', svl_to_vacuum.id)
|
||||||
]
|
]
|
||||||
candidates = self.env['stock.valuation.layer'].search(domain)
|
candidates = self.env['stock.valuation.layer'].sudo().search(domain)
|
||||||
if not candidates:
|
if not candidates:
|
||||||
continue
|
continue
|
||||||
qty_to_take_on_candidates = abs(svl_to_vacuum.remaining_qty)
|
qty_to_take_on_candidates = abs(svl_to_vacuum.remaining_qty)
|
||||||
@@ -359,7 +365,7 @@ class ProductProduct(models.Model):
|
|||||||
'company_id': move.company_id.id,
|
'company_id': move.company_id.id,
|
||||||
'description': 'Revaluation of %s (negative inventory)' % move.picking_id.name or move.name,
|
'description': 'Revaluation of %s (negative inventory)' % move.picking_id.name or move.name,
|
||||||
}
|
}
|
||||||
vacuum_svl = self.env['stock.valuation.layer'].create(vals)
|
vacuum_svl = self.env['stock.valuation.layer'].sudo().create(vals)
|
||||||
|
|
||||||
# Create the account move.
|
# Create the account move.
|
||||||
if self.valuation != 'real_time':
|
if self.valuation != 'real_time':
|
||||||
@@ -683,7 +689,7 @@ class ProductCategory(models.Model):
|
|||||||
% (self.display_name, self.property_valuation, new_valuation)
|
% (self.display_name, self.property_valuation, new_valuation)
|
||||||
out_svl_vals_list, products_orig_quantity_svl, products = Product\
|
out_svl_vals_list, products_orig_quantity_svl, products = Product\
|
||||||
._svl_empty_stock(description, product_category=product_category)
|
._svl_empty_stock(description, product_category=product_category)
|
||||||
out_stock_valuation_layers = SVL.create(out_svl_vals_list)
|
out_stock_valuation_layers = SVL.sudo().create(out_svl_vals_list)
|
||||||
if product_category.property_valuation == 'real_time':
|
if product_category.property_valuation == 'real_time':
|
||||||
move_vals_list += Product._svl_empty_stock_am(out_stock_valuation_layers)
|
move_vals_list += Product._svl_empty_stock_am(out_stock_valuation_layers)
|
||||||
impacted_categories[product_category] = (products, description, products_orig_quantity_svl)
|
impacted_categories[product_category] = (products, description, products_orig_quantity_svl)
|
||||||
@@ -693,7 +699,7 @@ class ProductCategory(models.Model):
|
|||||||
for product_category, (products, description, products_orig_quantity_svl) in impacted_categories.items():
|
for product_category, (products, description, products_orig_quantity_svl) in impacted_categories.items():
|
||||||
# Replenish the stock with the new cost method.
|
# Replenish the stock with the new cost method.
|
||||||
in_svl_vals_list = products._svl_replenish_stock(description, products_orig_quantity_svl)
|
in_svl_vals_list = products._svl_replenish_stock(description, products_orig_quantity_svl)
|
||||||
in_stock_valuation_layers = SVL.create(in_svl_vals_list)
|
in_stock_valuation_layers = SVL.sudo().create(in_svl_vals_list)
|
||||||
if product_category.property_valuation == 'real_time':
|
if product_category.property_valuation == 'real_time':
|
||||||
move_vals_list += Product._svl_replenish_stock_am(in_stock_valuation_layers)
|
move_vals_list += Product._svl_replenish_stock_am(in_stock_valuation_layers)
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class StockMove(models.Model):
|
|||||||
if forced_quantity:
|
if forced_quantity:
|
||||||
svl_vals['description'] = 'Correction of %s (modification of past move)' % move.picking_id.name or move.name
|
svl_vals['description'] = 'Correction of %s (modification of past move)' % move.picking_id.name or move.name
|
||||||
svl_vals_list.append(svl_vals)
|
svl_vals_list.append(svl_vals)
|
||||||
return self.env['stock.valuation.layer'].create(svl_vals_list)
|
return self.env['stock.valuation.layer'].sudo().create(svl_vals_list)
|
||||||
|
|
||||||
def _create_out_svl(self, forced_quantity=None):
|
def _create_out_svl(self, forced_quantity=None):
|
||||||
"""Create a `stock.valuation.layer` from `self`.
|
"""Create a `stock.valuation.layer` from `self`.
|
||||||
@@ -179,7 +179,7 @@ class StockMove(models.Model):
|
|||||||
if forced_quantity:
|
if forced_quantity:
|
||||||
svl_vals['description'] = 'Correction of %s (modification of past move)' % move.picking_id.name or move.name
|
svl_vals['description'] = 'Correction of %s (modification of past move)' % move.picking_id.name or move.name
|
||||||
svl_vals_list.append(svl_vals)
|
svl_vals_list.append(svl_vals)
|
||||||
return self.env['stock.valuation.layer'].create(svl_vals_list)
|
return self.env['stock.valuation.layer'].sudo().create(svl_vals_list)
|
||||||
|
|
||||||
def _create_dropshipped_svl(self, forced_quantity=None):
|
def _create_dropshipped_svl(self, forced_quantity=None):
|
||||||
"""Create a `stock.valuation.layer` from `self`.
|
"""Create a `stock.valuation.layer` from `self`.
|
||||||
@@ -218,7 +218,7 @@ class StockMove(models.Model):
|
|||||||
}
|
}
|
||||||
out_vals.update(common_vals)
|
out_vals.update(common_vals)
|
||||||
svl_vals_list.append(out_vals)
|
svl_vals_list.append(out_vals)
|
||||||
return self.env['stock.valuation.layer'].create(svl_vals_list)
|
return self.env['stock.valuation.layer'].sudo().create(svl_vals_list)
|
||||||
|
|
||||||
def _create_dropshipped_returned_svl(self, forced_quantity=None):
|
def _create_dropshipped_returned_svl(self, forced_quantity=None):
|
||||||
"""Create a `stock.valuation.layer` from `self`.
|
"""Create a `stock.valuation.layer` from `self`.
|
||||||
@@ -242,7 +242,7 @@ class StockMove(models.Model):
|
|||||||
|
|
||||||
res = super(StockMove, self)._action_done(cancel_backorder=cancel_backorder)
|
res = super(StockMove, self)._action_done(cancel_backorder=cancel_backorder)
|
||||||
|
|
||||||
stock_valuation_layers = self.env['stock.valuation.layer']
|
stock_valuation_layers = self.env['stock.valuation.layer'].sudo()
|
||||||
# Create the valuation layers in batch by calling `moves._create_valued_type_svl`.
|
# Create the valuation layers in batch by calling `moves._create_valued_type_svl`.
|
||||||
for valued_type in self._get_valued_types():
|
for valued_type in self._get_valued_types():
|
||||||
todo_valued_moves = valued_moves[valued_type]
|
todo_valued_moves = valued_moves[valued_type]
|
||||||
@@ -260,8 +260,9 @@ class StockMove(models.Model):
|
|||||||
|
|
||||||
# For every in move, run the vacuum for the linked product.
|
# For every in move, run the vacuum for the linked product.
|
||||||
products_to_vacuum = valued_moves['in'].mapped('product_id')
|
products_to_vacuum = valued_moves['in'].mapped('product_id')
|
||||||
|
company = valued_moves['in'].mapped('company_id') and valued_moves['in'].mapped('company_id')[0] or self.env.company
|
||||||
for product_to_vacuum in products_to_vacuum:
|
for product_to_vacuum in products_to_vacuum:
|
||||||
product_to_vacuum._run_fifo_vacuum()
|
product_to_vacuum._run_fifo_vacuum(company)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ class TestStockValuation(SavepointCase):
|
|||||||
'type': 'product',
|
'type': 'product',
|
||||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||||
})
|
})
|
||||||
|
cls.inventory_user = cls.env['res.users'].create({
|
||||||
|
'name': 'Pauline Poivraisselle',
|
||||||
|
'login': 'pauline',
|
||||||
|
'email': 'p.p@example.com',
|
||||||
|
'notification_type': 'inbox',
|
||||||
|
'groups_id': [(6, 0, [cls.env.ref('stock.group_stock_user').id])]
|
||||||
|
})
|
||||||
|
|
||||||
cls.product1.categ_id.property_valuation = 'real_time'
|
cls.product1.categ_id.property_valuation = 'real_time'
|
||||||
cls.product2.categ_id.property_valuation = 'real_time'
|
cls.product2.categ_id.property_valuation = 'real_time'
|
||||||
@@ -2674,6 +2681,46 @@ class TestStockValuation(SavepointCase):
|
|||||||
self.assertAlmostEqual(self.product1.quantity_svl, 0.0)
|
self.assertAlmostEqual(self.product1.quantity_svl, 0.0)
|
||||||
self.assertAlmostEqual(self.product1.value_svl, 0.0)
|
self.assertAlmostEqual(self.product1.value_svl, 0.0)
|
||||||
|
|
||||||
|
def test_standard_manual_2(self):
|
||||||
|
"""Validate a receipt as a regular stock user."""
|
||||||
|
self.product1.categ_id.property_cost_method = 'standard'
|
||||||
|
self.product1.categ_id.property_valuation = 'manual_periodic'
|
||||||
|
|
||||||
|
self.product1.with_user(self.inventory_user)._change_standard_price(10)
|
||||||
|
|
||||||
|
move1 = self.env['stock.move'].with_user(self.inventory_user).create({
|
||||||
|
'name': 'IN 10 units',
|
||||||
|
'location_id': self.supplier_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,
|
||||||
|
})
|
||||||
|
move1._action_confirm()
|
||||||
|
move1._action_assign()
|
||||||
|
move1.move_line_ids.qty_done = 10.0
|
||||||
|
move1._action_done()
|
||||||
|
|
||||||
|
def test_standard_perpetual_1(self):
|
||||||
|
"""Validate a receipt as a regular stock user."""
|
||||||
|
self.product1.categ_id.property_cost_method = 'standard'
|
||||||
|
self.product1.categ_id.property_valuation = 'real_time'
|
||||||
|
|
||||||
|
self.product1.with_user(self.inventory_user)._change_standard_price(10)
|
||||||
|
|
||||||
|
move1 = self.env['stock.move'].with_user(self.inventory_user).create({
|
||||||
|
'name': 'IN 10 units',
|
||||||
|
'location_id': self.supplier_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,
|
||||||
|
})
|
||||||
|
move1._action_confirm()
|
||||||
|
move1._action_assign()
|
||||||
|
move1.move_line_ids.qty_done = 10.0
|
||||||
|
move1._action_done()
|
||||||
|
|
||||||
def test_change_cost_method_1(self):
|
def test_change_cost_method_1(self):
|
||||||
""" Change the cost method from FIFO to AVCO.
|
""" Change the cost method from FIFO to AVCO.
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user